浓缩解读前端系列书籍

浓缩解读《JavaScript设计模式与开发实践》③

2017-01-12  本文已影响465人  梁同学de自言自语

三、闭包和高阶函数

IMG_20170112_004128.jpg

3.1 闭包

3.1.1 变量的作用域

3.1.2 变量的生命周期

<script type="text/javascript">
    //现在有一个名为func的函数
    var func = function(){
        //①函数执行体中,将局部变量a赋值为1
        var a = 1;
        //②返回一个function执行环境
        return function(){
            //③执行环境中,将func.a局部变量加1,然后输出到控制台
            a++;
            console.info(a);
        }
    };
    
    //调用:将func函数执行后的返回,赋值给f
    var f = func();
    f();    //f()调用一次,输出2
    f();    //f()再调用一次,输出3
    f();    //f()接着调用,输出4
</script>

3.1.3 闭包的用途

  1. 封装变量:通过闭包将不需要暴露的变量封装成“私有变量”
var person = (function(){
    var name = "William";
    return function(){
        console.info(name);          
    };
})();
person();   // 输出成功
console.info(person.name);  //// 输出失败
  1. 延续变量的生命周期:我们经常用<img>标签进行数据上报,创建一个临时的img标签,将需要上报的数据附加在img的url后缀,从而上送到服务器。如例子所示:
var report = function(dataSrc){
    var img = new Image();  //创建image对象
    img.src = dataSrc;  //将要上送的数据url赋值给img的url
};
report('http://xxx.com/uploadUserData?name=william');
//注意:我们将普通函数改成了自执行函数
var report = (function(){
    var imgs = [];
    return function(dataSrc){
        var img = new Image();
        images.push(img);
        img.src = dataSrc;
    }
})();
  1. 用闭包实现面向对象:我们经常使用过程数据来描述面向对象编程当中的对象。对象的方法包含了过程,而闭包则是在过程中以执行环境的方式包含了数据。
//Person构造器,里面有一个name属性
var Person = function(){
  this.name = "William";
};
//给Person的原型添加一个sayName()方法
Person.prototype.sayName = function(){
    console.info("hello,my name is " + this.name);
};
//实例化Person
var person1 = new Person();
person1.sayName();
//person()函数返回一个有sayName()方法的对象
var person = function(){
    var name = "William";
    return {
        sayName : function(){
            console.info("hello,my name is " + name);
        }
    }
};
//执行person()函数,将返回的对象赋值给person1
var person1 = person();
//调用person1.sayName()方法
person1.sayName();
//控制台输出 "hello,my name is William"
  1. 用闭包实现命令模式
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <button id="execute">开启</button>
        <button id="undo">关闭</button>
        <script type="text/javascript">
            var Tv = {
                open : function(){
                    console.info("打开电视机");
                },
                close : function(){
                    console.info("关闭电视机");
                }
            };
            var OpenTvCommand = function(receiver){
                this.receiver = receiver;
            };
            OpenTvCommand.prototype.execute = function(){
                this.receiver.open();
            };
            OpenTvCommand.prototype.undo = function(){
                this.receiver.close();
            };
            var setCommand = function(command){
                document.getElementById("execute").onclick = function(){
                    command.execute();
                }
                document.getElementById("undo").onclick = function(){
                    command.undo();
                }
            };
            //调用
            setCommand(new OpenTvCommand(Tv));
        </script>
    </body>
</html>
<script type="text/javascript">
    var Tv = {
        open : function(){
            console.info("打开电视机");
        },
        close : function(){
            console.info("关闭电视机");
        }
    };
    var createCommand = function(receiver){
        var execute = function(){
            return receiver.open();
        }
        var undo = function(){
            return receiver.close();
        }
        return {
            execute : execute,
            undo : undo
        }
    };
    var setCommand = function(command){
        document.getElementById("execute").onclick = function(){
            command.execute();
        }
        document.getElementById("undo").onclick = function(){
            command.undo();
        }
    };
    //调用
    setCommand(createCommand(Tv));
</script>
3.1.4 闭包与内存管理

3.2 高阶函数

  1. 函数可以作为参数被传递;
  2. 函数可以作为返回值输出;
3.2.1 函数作为参数传入
//按钮监听事件
$("btn").click(function(){
  console.info("btn clicked");
});
//可以发现,其本质就是执行了click()方法,然后传入一个函数作为参数。
//注意到:在按钮点击后的处理是变化的,通过回调函数来封装变化。
var arr = [1,7,9,2];
//从小到大排序
arr.sort(function(){
  return a - b;
});
console.info(arr); //输出 "[1, 2, 7, 9]"
//从大道小排序
arr.sort(function(){
  return b - a;
});
console.info(arr); //输出 "[9, 7, 2, 1]"
3.2.2 函数作为返回值输出
//判断是否为String
var isString = function(obj){
    //通过传入的obj对象执行toString()方法,将结果值和预期字符串比较
    return Object.prototype.toString.call(obj) === '[object String]';
}
//判断是否为数组
var isArray = function(obj){
    return Object.prototype.toString.call(obj) === '[object Array]';
}
//判断是否为数字
var isNumber = function(obj){
    return Object.prototype.toString.call(obj) === '[object Number]';
}
//抽象出一个类型判断的通用函数
var isType = funcion(type){
    //该函数返回一个可执行的函数,用来执行toString方法和预期字符串做比较
    return function(obj){
        return Object.prototype.toString.call(obj) === '[Object '+type+']';
    }
}
//预先注册具体的类型判断方法
var isString = isType("String");
var isArray = isType("Array");
var isNumber = isType("Number");
//调用
console.info(isArray([1,3,2]));  //输出: true
var getSingle = function(fn){
    var ret;  //临时变量
    return function(){
        //如果ret已经存在的话则返回;否则新创建对象
        return ret || (ret = fn.apply(this,arguments));
    }
}
var getScript = getSingle(function(){
    return document.createElement('script');
});
var script1 = getScript();
var script2 = getScript();
console.info(script1 === script2);//输出: true
3.2.3 高阶函数实现AOP
Function.prototype.invokeBefore = function(beforFn){
    var _self = this;   //原函数的引用
    return function(){
        //先执行传入的before函数
        beforFn.apply(this,arguments);
        //然后再执行自身
        return _self.apply(this,arguments);
    }
}
Function.prototype.invokeAfter = function(afterFn){
    var _self = this;   //
    return function(){
        //先执行函数,并保存执行结果
        var ret = _self.apply(this,arguments);
        //然后再执行after函数
        afterFn.apply(this,arguments);
        //最后返回结果
        return ret;
    }
}
//定义一个方法,控制台输出2
var func = function(){
    console.info(2);
};
//指定func()函数执行前和执行后要做的事情
func = func.invokeBefore(function(){
    console.info(1);
}).invokeAfter(function(){
    console.info(3);
});
//调用func()函数,控制台输出 1 2 3
func();
3.2.4 高阶函数实现柯里化
var currying = function(fn){
    var args = [];  //缓存对象
    return function(){
        if(arguments.length == 0){
            //如果传入的参数为空,则直接返回结果
            return fn.apply(this,args);
        }else{
            //如果参数不为空,则将传入参数push到args数组中缓存起来
            [].push.apply(args,arguments);
            //并返回函数本身
            return arguments.callee;
        }
    }
}
var cost = (function(){
    var money = 0;
    return function(){
        for(var i=0;l = arguments.length;i<l;i++){
            money += arguments[i];
        }
        return money;
    }
});
//转换成currying函数
var cost = currying(cost);
cost(100);  //记账100,未真正求值
cost(100);  //记账100,未真正求值
cost(400);  //记账400,未真正求值
console.info(cost());   //求值,并输出:600
3.2.5 高阶函数实现反柯里化
//为Function对象的原型添加uncurrying方法
Function.prototype.uncurrying = function(){
    var self = this;
    return function(){
        var obj = Array.prototype.call(arguments);
        return self.apply(obj,arguments);
    }
}
//提取push方法并使用
var push = Array.prototype.uncurrying();
(function(){
    push(arguments,4);
    console.info(arguments);//输出 [1,2,3,4]
})(1,2,3);
3.2.6 高阶函数实现函数节流
var throttle = function(fn,interval){
    var _self = fn,
            timer,
            firstTime = true;
    return function(){
        var args = arguments,
                _me = this;
        if(firstTime){
            _self.apply(_me,args);
            return firstTime = false;
        }
        if(timer){
            return false;
        }
        timer = setTimeout(function(){
            clearTimeout(timer);
            timer = null;
            _self.apply(_me,args);
        },interval || 500);
    };
};
window.onresize = throttle(function(){
    console.info("resize come in");
},500);
3.2.7 高阶函数实现分时函数
//模拟添加1000个数据
var ary = [];
for (var i=1;i<=1000;i++) {
    ary.push(i);
};
var renderFriendList = function(data){
    for (var i=0;l=data.length;i<l;i++) {
        var div = document.createElement('div');
        div.innerHTML = i;
        document.body.appendChild(div);
    }
};
renderFriendList(ary);
//创建timeChunk函数
var timeChunk = function(ary,fn,count){
    var obj,t,len = ary.length;
    var start = function(){
        for (var i=0;i<Math.min(count || 1,ary.length);i++) {
            var obj = ary.shift();
            fn(obj);
        }
    }
    return function(){
        t = setInterval(function(){
            if(ary.length === 0){
                return clearInterval(t);
            }
            start();
        },200);
    }
};
//测试
var ary = [];
for (var i=1;i<=1000;i++) {
    ary.push(i);
};
var renderFriendList = timeChunk(ary,function(n){
    var div = document.createElement('div');
    div.innerHTML = i;
    document.body.appendChild(div);
},8);
renderFriendList(ary);

3.1 闭包

3.1.1 变量的作用域

3.1.2 变量的生命周期

<script type="text/javascript">
    //现在有一个名为func的函数
    var func = function(){
        //①函数执行体中,将局部变量a赋值为1
        var a = 1;
        //②返回一个function执行环境
        return function(){
            //③执行环境中,将func.a局部变量加1,然后输出到控制台
            a++;
            console.info(a);
        }
    };
    
    //调用:将func函数执行后的返回,赋值给f
    var f = func();
    f();    //f()调用一次,输出2
    f();    //f()再调用一次,输出3
    f();    //f()接着调用,输出4
</script>

3.1.3 闭包的用途

  1. 封装变量:通过闭包将不需要暴露的变量封装成“私有变量”
var person = (function(){
    var name = "William";
    return function(){
        console.info(name);          
    };
})();
person();   // 输出成功
console.info(person.name);  //// 输出失败
  1. 延续变量的生命周期:我们经常用<img>标签进行数据上报,创建一个临时的img标签,将需要上报的数据附加在img的url后缀,从而上送到服务器。如例子所示:
var report = function(dataSrc){
    var img = new Image();  //创建image对象
    img.src = dataSrc;  //将要上送的数据url赋值给img的url
};
report('http://xxx.com/uploadUserData?name=william');
//注意:我们将普通函数改成了自执行函数
var report = (function(){
    var imgs = [];
    return function(dataSrc){
        var img = new Image();
        images.push(img);
        img.src = dataSrc;
    }
})();
  1. 用闭包实现面向对象:我们经常使用过程数据来描述面向对象编程当中的对象。对象的方法包含了过程,而闭包则是在过程中以执行环境的方式包含了数据。
//Person构造器,里面有一个name属性
var Person = function(){
  this.name = "William";
};
//给Person的原型添加一个sayName()方法
Person.prototype.sayName = function(){
    console.info("hello,my name is " + this.name);
};
//实例化Person
var person1 = new Person();
person1.sayName();
//person()函数返回一个有sayName()方法的对象
var person = function(){
    var name = "William";
    return {
        sayName : function(){
            console.info("hello,my name is " + name);
        }
    }
};
//执行person()函数,将返回的对象赋值给person1
var person1 = person();
//调用person1.sayName()方法
person1.sayName();
//控制台输出 "hello,my name is William"
  1. 用闭包实现命令模式
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <button id="execute">开启</button>
        <button id="undo">关闭</button>
        <script type="text/javascript">
            var Tv = {
                open : function(){
                    console.info("打开电视机");
                },
                close : function(){
                    console.info("关闭电视机");
                }
            };
            var OpenTvCommand = function(receiver){
                this.receiver = receiver;
            };
            OpenTvCommand.prototype.execute = function(){
                this.receiver.open();
            };
            OpenTvCommand.prototype.undo = function(){
                this.receiver.close();
            };
            var setCommand = function(command){
                document.getElementById("execute").onclick = function(){
                    command.execute();
                }
                document.getElementById("undo").onclick = function(){
                    command.undo();
                }
            };
            //调用
            setCommand(new OpenTvCommand(Tv));
        </script>
    </body>
</html>
<script type="text/javascript">
    var Tv = {
        open : function(){
            console.info("打开电视机");
        },
        close : function(){
            console.info("关闭电视机");
        }
    };
    var createCommand = function(receiver){
        var execute = function(){
            return receiver.open();
        }
        var undo = function(){
            return receiver.close();
        }
        return {
            execute : execute,
            undo : undo
        }
    };
    var setCommand = function(command){
        document.getElementById("execute").onclick = function(){
            command.execute();
        }
        document.getElementById("undo").onclick = function(){
            command.undo();
        }
    };
    //调用
    setCommand(createCommand(Tv));
</script>
3.1.4 闭包与内存管理

3.2 高阶函数

  1. 函数可以作为参数被传递;
  2. 函数可以作为返回值输出;
3.2.1 函数作为参数传入
//按钮监听事件
$("btn").click(function(){
  console.info("btn clicked");
});
//可以发现,其本质就是执行了click()方法,然后传入一个函数作为参数。
//注意到:在按钮点击后的处理是变化的,通过回调函数来封装变化。
var arr = [1,7,9,2];
//从小到大排序
arr.sort(function(){
  return a - b;
});
console.info(arr); //输出 "[1, 2, 7, 9]"
//从大道小排序
arr.sort(function(){
  return b - a;
});
console.info(arr); //输出 "[9, 7, 2, 1]"
3.2.2 函数作为返回值输出
//判断是否为String
var isString = function(obj){
    //通过传入的obj对象执行toString()方法,将结果值和预期字符串比较
    return Object.prototype.toString.call(obj) === '[object String]';
}
//判断是否为数组
var isArray = function(obj){
    return Object.prototype.toString.call(obj) === '[object Array]';
}
//判断是否为数字
var isNumber = function(obj){
    return Object.prototype.toString.call(obj) === '[object Number]';
}
//抽象出一个类型判断的通用函数
var isType = funcion(type){
    //该函数返回一个可执行的函数,用来执行toString方法和预期字符串做比较
    return function(obj){
        return Object.prototype.toString.call(obj) === '[Object '+type+']';
    }
}
//预先注册具体的类型判断方法
var isString = isType("String");
var isArray = isType("Array");
var isNumber = isType("Number");
//调用
console.info(isArray([1,3,2]));  //输出: true
var getSingle = function(fn){
    var ret;  //临时变量
    return function(){
        //如果ret已经存在的话则返回;否则新创建对象
        return ret || (ret = fn.apply(this,arguments));
    }
}
var getScript = getSingle(function(){
    return document.createElement('script');
});
var script1 = getScript();
var script2 = getScript();
console.info(script1 === script2);//输出: true
3.2.3 高阶函数实现AOP
Function.prototype.invokeBefore = function(beforFn){
    var _self = this;   //原函数的引用
    return function(){
        //先执行传入的before函数
        beforFn.apply(this,arguments);
        //然后再执行自身
        return _self.apply(this,arguments);
    }
}
Function.prototype.invokeAfter = function(afterFn){
    var _self = this;   //
    return function(){
        //先执行函数,并保存执行结果
        var ret = _self.apply(this,arguments);
        //然后再执行after函数
        afterFn.apply(this,arguments);
        //最后返回结果
        return ret;
    }
}
//定义一个方法,控制台输出2
var func = function(){
    console.info(2);
};
//指定func()函数执行前和执行后要做的事情
func = func.invokeBefore(function(){
    console.info(1);
}).invokeAfter(function(){
    console.info(3);
});
//调用func()函数,控制台输出 1 2 3
func();
3.2.4 高阶函数实现柯里化
var currying = function(fn){
    var args = [];  //缓存对象
    return function(){
        if(arguments.length == 0){
            //如果传入的参数为空,则直接返回结果
            return fn.apply(this,args);
        }else{
            //如果参数不为空,则将传入参数push到args数组中缓存起来
            [].push.apply(args,arguments);
            //并返回函数本身
            return arguments.callee;
        }
    }
}
var cost = (function(){
    var money = 0;
    return function(){
        for(var i=0;l = arguments.length;i<l;i++){
            money += arguments[i];
        }
        return money;
    }
});
//转换成currying函数
var cost = currying(cost);
cost(100);  //记账100,未真正求值
cost(100);  //记账100,未真正求值
cost(400);  //记账400,未真正求值
console.info(cost());   //求值,并输出:600
3.2.5 高阶函数实现反柯里化
//为Function对象的原型添加uncurrying方法
Function.prototype.uncurrying = function(){
    var self = this;
    return function(){
        var obj = Array.prototype.call(arguments);
        return self.apply(obj,arguments);
    }
}
//提取push方法并使用
var push = Array.prototype.uncurrying();
(function(){
    push(arguments,4);
    console.info(arguments);//输出 [1,2,3,4]
})(1,2,3);
3.2.6 高阶函数实现函数节流
var throttle = function(fn,interval){
    var _self = fn,
            timer,
            firstTime = true;
    return function(){
        var args = arguments,
                _me = this;
        if(firstTime){
            _self.apply(_me,args);
            return firstTime = false;
        }
        if(timer){
            return false;
        }
        timer = setTimeout(function(){
            clearTimeout(timer);
            timer = null;
            _self.apply(_me,args);
        },interval || 500);
    };
};
window.onresize = throttle(function(){
    console.info("resize come in");
},500);
3.2.7 高阶函数实现分时函数
//模拟添加1000个数据
var ary = [];
for (var i=1;i<=1000;i++) {
    ary.push(i);
};
var renderFriendList = function(data){
    for (var i=0;l=data.length;i<l;i++) {
        var div = document.createElement('div');
        div.innerHTML = i;
        document.body.appendChild(div);
    }
};
renderFriendList(ary);
//创建timeChunk函数
var timeChunk = function(ary,fn,count){
    var obj,t,len = ary.length;
    var start = function(){
        for (var i=0;i<Math.min(count || 1,ary.length);i++) {
            var obj = ary.shift();
            fn(obj);
        }
    }
    return function(){
        t = setInterval(function(){
            if(ary.length === 0){
                return clearInterval(t);
            }
            start();
        },200);
    }
};
//测试
var ary = [];
for (var i=1;i<=1000;i++) {
    ary.push(i);
};
var renderFriendList = timeChunk(ary,function(n){
    var div = document.createElement('div');
    div.innerHTML = i;
    document.body.appendChild(div);
},8);
renderFriendList(ary);
上一篇 下一篇

猜你喜欢

热点阅读