第 3 章 闭包和高阶函数
第一部分 基础知识
第 3 章 闭包和高阶函数
3.1 闭包
3.1.1 变量的作用域
变量的作用域,就是指变量的有效范围。
//作用域范围
var func = function(){
var a = 1;
alert ( a ); // 输出: 1
};
func();
alert ( a ); // 输出:Uncaught ReferenceError: a is not defined
//嵌套函数
var a = 1;
var func1 = function(){
var b = 2;
var func2 = function(){
var c = 3;
alert ( b ); // 输出:2
alert ( a ); // 输出:1
}
func2();
alert ( c ); // 输出:Uncaught ReferenceError: c is not defined
};
func1();
3.1.2 变量的生存周期
对于全局变量来说,全局变量的生存周期是永久的;
而对于在函数内用 var 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁。
//生命周期
var func = function(){
var a = 1; // 退出函数后局部变量 a 将被销毁
alert ( a );
};
func();
3.1.3 闭包的更多作用
1. 封装变量
//1. 封装变量
var mult = (function(){
var cache = {};
var calculate = function(){
var a = 1;
for(var i = 0,l = arguments.length;i<l;i++){
a = a * arguments[i];
console.log(i)
}
return a;
}
return function(){
var args = Array.prototype.join.call(arguments, ',');
if(args in cache){
return cache[args];
}
return cache[args] = calculate.apply(null, arguments);
}
})()
console.log(mult( 1,2,3 ))
console.log(mult( 1,2,3 ))
2. 延续局部变量的寿命
3.1.4 闭包和面向对象设计
3.1.5 用闭包实现命令模式
命令模式的意图是把请求封装为对象,从而分离请求的发起者和请求的接收者(执行者)之间的耦合关系。在命令被执行之前,可以预先往命令对象中植入命令的接收者。
<!--命令模式--> <div> <button id='open'>打开</button> <button id='close'>关闭</button> </div>
//命令模式
var Tv = {
open:function(){
console.log("打开电视机");
},
close:function(){
console.log("关闭电视机");
}
}
var creatCommand = function(receiver){
var open = function(){
return receiver.open();
}
var close = function(){
return receiver.close();
}
return {
open:open,
close:close
}
}
var setCommand = function(command){
document.getElementById("open").onclick = function(){
command.open();
}
document.getElementById("close").onclick = function(){
command.close();
}
}
setCommand(creatCommand(Tv))
3.1.6 闭包与内存管理
如果要解决循环引用带来的内存泄露问题,我们只需要把循环引用中的变量设为 null即可。
3.2 高阶函数
高阶函数是指至少满足下列条件之一的函数。
3.2.1 函数可以作为参数被传递;
1. 回调函数
2. Array.prototype.sort
3.2.2 函数可以作为返回值输出。
1. 判断数据的类型
//判断数据的类型
var isType = function( type ){
return function( obj ){
return Object.prototype.toString.call( obj ) === '[object '+ type +']';
}
};
var isString = isType( 'String' );
var isArray = isType( 'Array' );
var isNumber = isType( 'Number' );
console.log( isArray( [ 1, 2, 3 ] ) ); // 输出:true
//判断数据的类型
var Type = {};
for(var i=0,type;type=['String','Array','Number'][i++];){
(function(type){
Type['is'+type] = function(obj){
return Object.prototype.toString.call(obj) === '[object '+type+']';
}
})(type)
}
console.log(Type.isArray([ 1, 2, 3 ] ));
console.log(Type.isString('str'));
2. getSingle单例模式
var getSingle = function(fn){
var ret;
return function(){
return ret || (ret = fn.apply(this, arguments));
}
}
var getScript = getSingle(function(){
return document.createElement('script');
})
var script1 = getScript();
console.log(script1);
3.2.3 高阶函数实现AOP
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,包括日志统计、安全控制、异常处理等。好处是保持业务逻辑模块的纯净和高内聚性。
在JavaScript中实现AOP,都是指把一个函数“动态织入”到另一个函数之中。
//通过扩展 Function.prototype来实现面向切面
Function.prototype.before = function(beforefn){
var _self = this; //保存原函数的引用
//返回包含原函数和新函数的“代理”函数
return function(){
beforefn.apply(this, arguments); //执行新函数,修正this
return _self.apply(this,arguments); //执行原函数
}
}
Function.prototype.after = function(afterfn){
var _self = this;
return function(){
var ref = _self.apply(this, arguments);
afterfn.apply(this.arguments);
return ref;
}
}
var func = function(){
console.log(2);
}
func = func.before(function(){
console.log(1);
}).after(function(){
console.log(3);
})
func();
3.2.4 高阶函数的其他应用
1. currying
函数柯里化(function currying):currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数后,该函数不会立即求值,而是返回另一个函数,刚才传入的参数在函数中形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性求值。
var currying = function(fn){
var args = [];
return function(){
if(arguments.length === 0){
return fn.apply(this, args);
}else{
[].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;
}
})()
var cost = currying(cost);//转化成currying函数
cost(600);//未真正求值
cost(900);//未真正求值
console.log(cost())//求值
2. uncurrying
在javaScript中,当我们调用对象的某个方法时,其实不用去关心该对象原本是否被设计为拥有该方法,这是动态类型语方的特点,也就是鸭子类型思想。一个对象未必只能使用它自身的方法,可以让对象去借用一个原本不属于它的方法。call和apply都可以完成这个需求:
//借用call、apply
var obj1 = {
name:'name1'
}
var obj2 = {
getName:function(){
return this.name;
}
}
console.log(obj2.getName.apply(obj1));//obj1借用obj2的getName方法
// Array.prototype
(function(){
Array.prototype.push.call(arguments,4,5);//arguments借用Array.prototype.push方法
console.log(arguments)
})(1,2,3)
//把泛化this的过程提取出来
Function.prototype.currying = function(){
var _self = this;// self 此时是 Array.prototype.push
return function(){
var obj = Array.prototype.shift.call(arguments);//arguments 对象的第一个元素被截去
return _self.apply(obj,arguments);// 相当于 Array.prototype.push.apply( obj, 2 )
}
}
for(var i=0,fn, ary = ['push','shift','forEach'];fn=ary[i++];){
Array[fn] = Array.prototype[fn].currying();
}
var obj = {
'length':'3',
'0':1,
'1':2,
'2':3
}
Array.push(obj,4);//向对象中添加一个元素
console.log(obj);
var first = Array.shift(obj);//截取第一个元素
console.log(first);//输出:1
Array.forEach(obj,function(item,i){
console.log(item);//输出2,3,4
})
//uncurrying另一种实现方式
Function.prototype.currying = function(){
var _self = this;
return function(){
return Function.prototype.call.apply(_self,arguments);
}
}
3. 函数节流
函数的触发不是由用户直接控制的时候,函数有可能会频繁的被调用,造成大的性能问题。
(1)函数被频繁调用的场景:
window.onresize 事件。当浏览器窗口大小被拖动而改变的时候。
mousemove 事件。拖 拽事件。
上传进度。
(2) 函数节流的原理:
上述三个场景面临的共同问题是函数被触发频率太高。可以借助setTimeout来完成这件事情。
(3) 函数节流的代码实现
//函数节流
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)
}
}
4. 分时函数
由于用户调用的,导致一些函数严重影响页面性能。
/*
* 分时函数
* ary:创建节点时用到的数据
* fn:封装创建节点辑的函数
* count每一批创建节点的数量
*/
var timeChunk = function(ary, fn, count){
var timer;
var start = function(){
for(var i = 0;i<Math.min(count || 1,ary.length);i++){
var obj = ary.shift();
fn(obj);
}
};
return function(){
timer = setInterval(function(){
if(ary.length === 0){//如果全部节点创建完毕
return clearInterval(timer);
}
start();
},200)
}
var ary = [];
for(var i = 0;i<1000;i++){
ary.push(i);
}
var renderFriendList = timeChunk(ary,function(node){
var div = document.createElement('div');
div.innerHTML = node;
document.body.appendChild(div);
},8);
renderFriendList();
5. 惰性加载函数
避免每次执行都会进行判断,让程序避免这些重复的执行过程。
//惰性加载函数
var addEvent = function(elem, type, handler){
if(window.addEventListener){
//对addEvent函数进行重写,当下次再进入addEvent的时候不再重复进行判断
addEvent = function(elem, type, handler){
elem.addEventListener(type, handler, false);
}
}else if(window.attachEvent){
addEvent = function(elem, type, handler){
elem.attachEvent('on'+type,handler,false);
}
}
addEvent(elem,type,handler);
};
var div = document.getElementById('div1');
addEvent(div,'click',function(){
console.log(1);
});
addEvent(div,'click',function(){
console.log(2);
});
3.3 小结
许多设计模式在 JavaScript 之中的实现跟在一些传统面向对象语言中的实现相差很大。在JavaScript 中,很多设计模式都是通过闭包和高阶函数实现的。
浙公网安备 33010602011771号