javascript 闭包

拭目以待 发布于

概念:

   源自javascript 权威指南中的闭包的定义:"函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。
   要明白闭包是什么,先要了解变量作用域。先来看一组简单的变量作用域。


变量作用域示例:

   闭包的核心是变量作用域,在调用函数时,函数中使用到的变量会优先在该函数中匹配对应的变量声明,如未找到则向上层寻找,直至全局变量。下面的示例可以很直接展现出变量在作用域的优先级。
var scopeVal = 'global';
function testScope(){
	var scopeVal = 'testScope'
	return function (){
		var scopeVal = 'test';
		return scopeVal;
	}
}
console.log('scope=',testScope()()); // =>test 返回的是被调用方法自身的变量  var scopeVal = 'test';
var scopeVal = 'global';
function testScope(){
	var scopeVal = 'testScope'
	return function (){
		return scopeVal;
	}
}
console.log('scope=',testScope()()); // =>testScope 返回的是testScope的变量  var scopeVal = 'testScope';
var scopeVal = 'global';
function testScope(){
	return function (){
		return scopeVal;
	}
}
console.log('scope=',testScope()()); // =>global 返回的是全局变量  var scopeVal = 'global';




接下来我们先写一个简单的闭包,这段code是比较典型的闭包。从这个示例中可以看出,num值在每次调用之后并未被销毁。

var getReadyNum = (function(){
	var num = 0;
	function getNum(){
		return num++;
	}
	return getNum;
})();
console.log(getReadyNum()); //0
console.log(getReadyNum()); //1
console.log(getReadyNum()); //2 从这里可以看出来,num这个值一保存在内存中,并未被销毁。


上段示例中,使用到了局部变量num。现在将该变量移除,使用参数。

//在声明函数时,所定义的形参可以理解为是该函数作用域下另外一种形式的变量声明方式,等同于var num。
function baukh(num){
	return {
		getNum:  function(){
			return typeof(num) == 'undefined' ? 0 : num++;
		}
		,resetNum: function(){			
			return num = 0;
		}
	}
}
//通过实参将 1 传入baukh()函数,相当于为局部变量赋值;
//baukh函数返回了一个对象,并且该对象赋值于变量t,该对象存在两个方法getNum,resetNum。该对象的原型指向函数Object.prototype。
var t = baukh(1);  
console.log(Object.getPrototypeOf(t) == Object.prototype)
console.log(t.getNum()); //1
console.log(t.getNum());  //2
console.log(t.resetNum()); //0
console.log(t.getNum());   //0


通过一个功能的实现来说明:

   实例功能:记录用户在进行站点后到产生一次成功的购买之前,都看过哪些商品。
   三种功能实现方式:1、最简易的实现;2、使用对像实现;3、使用闭包实现;
   通过这个简单的示例,揭示了闭包如何使用。从个人习惯上讲,我更习惯使用对象方式来进行编码。

1、最简易的实现:

//未使用闭包,使用全局变量进行存储。全局变量,理论上讲应该是需要慎用的。
var globalRecordOperationList = [];
function globalRecordOperation(action){
	//获取浏览记录
	if(typeof(action) !== 'string'){ 
		return globalRecordOperationList;
	//存储浏览记录
	}else{	
		globalRecordOperationList.push(action)
	}
}
//进入站点,并开始浏览
globalRecordOperation('Thinkpad E550C');
globalRecordOperation('Thinkpad E430');
globalRecordOperation('Thinkpad E450');
globalRecordOperation('macbook pro');
globalRecordOperation('Thinkpad E430');
var globalUserActionList = globalRecordOperation(); 
console.log(globalUserActionList); //=> ["Thinkpad E550C", "Thinkpad E430", "Thinkpad E450", "macbook pro", "Thinkpad E430"]



2、使用对象实现:

//这段code是对象式编程
var recordOperationObject = {
	operationList : []
	,get operation(){
		return this.operationList;
	}
	,set operation(action){
		this.operationList.push(action)
	}
}
recordOperationObject.operation = 'Thinkpad E550C';
recordOperationObject.operation = 'Thinkpad E430';
recordOperationObject.operation = 'Thinkpad E450';
recordOperationObject.operation = 'macbook pro';
recordOperationObject.operation = 'Thinkpad E430';
var userActionListForObject = recordOperationObject.operation;
console.log(userActionListForObject);//=>["Thinkpad E550C", "Thinkpad E430", "Thinkpad E450", "macbook pro", "Thinkpad E430"]



3、使用闭包实现:

//这段code是函数式编辑
function recordOperation (){
	var operationList = [];
	return function(action){
		//获取浏览记录
		if(typeof(action) !== 'string'){ 
			return operationList;
		//存储浏览记录
		}else{	
			operationList.push(action)
		}
	}
}
var userAction = recordOperation();
//进入站点,并开始浏览
userAction('Thinkpad E550C');
userAction('Thinkpad E430');
userAction('Thinkpad E450');
userAction('macbook pro');
userAction('Thinkpad E430');
//购买产生,获取浏览记录
var userActionList = userAction();  
console.log(userActionList); // =>  ["Thinkpad E550C", "Thinkpad E430", "Thinkpad E450", "macbook pro", "Thinkpad E430"]


需注意:在闭包中使用循环时容易出现的问题:

//错误的方式:执行后,结果并不是想像中的2 这是由于for代码块实际上是动态创建了5个闭包,而这五个闭包共享变量i,在外部调用时,这个变量i的值是循环结束时的5。
function testFor(){
    var tr = [];
    for(var i=0; i<5; i++){
      tr[i] = function(){
          return i;
        }
    }
    return tr;
}
var tr = testFor();
console.log(tr[2]());//5
//正确的方式:执行后,与期望值相同。
function testFor2(n){	
    return function(){
	return n;
    }
}
var tr2 = [];
for (var i =0; i < 5; i++ ){
	tr2[i] = testFor2(i);
};
console.log(tr2[2]());		//2