想要彻底理解闭包,就必须理解如何创建作用域链以及作用域链有什么作用。
一般情况下,以compare函数为例:
function compare(val1,val2) {
return val1 < val2 ? 1 : 0;
}
var result = compare(1,3);
后台中每一个执行环境都有一个表示变量的对象叫做变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部环境中的变量对象只有在函数执行时才会存在。
在创建这个compare函数的时候,会创建一个预先包含全局的变量对象的作用域链,这个作用域链被保存在内部的[[Scopes]]属性中。(订正一下,《高级程序设计3》中此处是[[Scope]],但是从chrome中检验的是[[Scopes]])。当这个函数被执行时,会为函数创建一个执行环境,并通过复制函数的[[Scopes]]属性中的对象构建执行环境的作用域链。这时又会有个一活动对象被创建并被被推入执行环境作用域链的最前端。 就compare函数来说,作用域链中包含着两个变量对象:本地活动对象和全局变量对象。作用域链本质上是一个指向变量对象的指针列表。
但闭包存在时,这种情况又会不同。
当匿名函数被返回后,他的作用域链中被初始化为包含creatComprasionFun函数中的活动对象和全局变量对象。这样匿名函数可以访问到其包含函数中的所有变量。重要的是,当creatComprasionFun执行完毕后,其执行环境的作用域链会被销毁,但是其活动对象仍保存在内存中。直到匿名函数被销毁后,createComprasionFun函数中的活动对象才被销毁。即;
还有一种情况需要注意,这也是初学js的同学懵逼的地方:闭包与变量的问题。即闭包只能取得包含函数中任意变量的最后一个值。闭包保存的是整个的变量对象而不是某一个特殊的值。
例子:
一般情况下,以compare函数为例:
function compare(val1,val2) {
return val1 < val2 ? 1 : 0;
}
var result = compare(1,3);
后台中每一个执行环境都有一个表示变量的对象叫做变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部环境中的变量对象只有在函数执行时才会存在。
在创建这个compare函数的时候,会创建一个预先包含全局的变量对象的作用域链,这个作用域链被保存在内部的[[Scopes]]属性中。(订正一下,《高级程序设计3》中此处是[[Scope]],但是从chrome中检验的是[[Scopes]])。当这个函数被执行时,会为函数创建一个执行环境,并通过复制函数的[[Scopes]]属性中的对象构建执行环境的作用域链。这时又会有个一活动对象被创建并被被推入执行环境作用域链的最前端。 就compare函数来说,作用域链中包含着两个变量对象:本地活动对象和全局变量对象。作用域链本质上是一个指向变量对象的指针列表。
下图表示在chrome中打印闭包,其中closure中即为闭包中引用的变量。
但闭包存在时,这种情况又会不同。
这里我们再创建一个比较函数:
function creatComparisonFun(propertyName){
// 可以定义私有变量,不会在函数之外暴露,但是可以通过返回的匿名函数访问或者设置data的属性值
var data = {};
return function (obj1,obj2){
var val1 = obj1[propertyName];
var val2 = obj2[propertyName];
return val1 < val2 ? 1 :0;
}
}
// 值得注意的地方,compare这才是一个闭包
var compare = creatComparisonFun('name');
var result = compare({name:'ww'},{name:'rr'});
当匿名函数被返回后,他的作用域链中被初始化为包含creatComprasionFun函数中的活动对象和全局变量对象。这样匿名函数可以访问到其包含函数中的所有变量。重要的是,当creatComprasionFun执行完毕后,其执行环境的作用域链会被销毁,但是其活动对象仍保存在内存中。直到匿名函数被销毁后,createComprasionFun函数中的活动对象才被销毁。即;
compare = null; // 解除对匿名函数的引用,以便释放内存。
注意:由于闭包会携带其包含函数的作用域,因此会比其他函数占用更多的内存。
还有一种情况需要注意,这也是初学js的同学懵逼的地方:闭包与变量的问题。即闭包只能取得包含函数中任意变量的最后一个值。闭包保存的是整个的变量对象而不是某一个特殊的值。
例子:
var aLi = []; // DOM中li合集
for(var i=0; i<aLi.length; i++){
aLi[i].onclick = function(){
console.log(i)
}
}
当点击的时候,打印出来的不是期望的每个li的index,而总是最后一个li的index。这就是因为闭包中只能取得其保存的整个变量对象中的i,这个i是最后一个i。关于解决方法可以使用一个自执行函数。
再举一个使用闭包的常用的例子(可以用来避免全局污染,定义不能被外部直接访问到的私有变量):
var dataFunc = (function(){
// 创建一个私有变量,外部是不能访问这个变量的
var obj = {};
// 创建一个函数,为这个私有变量添加一些访问私有变量的方法
return function(key,val){
return val? obj[key] = val : obj[key]; // 判断是获取属性还是设置属性
}
})()
通过这种闭包方式我们不能直接访问到其中的obj变量,却可以对其进行属性的获取、设置操作。