闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式,就是在一个函数内部创建另一个函数。
之所以一个内部的函数可以访问其外部的变量,而且在其被返回或是调用时还可以访问,是因为这个内部函数的作用域链中包含外部函数的作用域。
知识储备
在了解闭包之前,先要熟悉以下几点:
1. 首先要理解执行环境,执行环境定义了变量或函数有权访问的其他数据。
2. 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
3. 每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入到一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。<!-- more -->
4. 当某个函数被调用时,会创建一个执行环境及其相应的作用域链。然后使用arguments
和其他命名参数的值来初始化函数的活动对象。在函数中,活动对象作为变量对象使用(作用域链是由每层的变量对象链起来的)。
5. 在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,直到作用域链终点即全局执行环境。
6. 作用域链的本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
1.一般情况下
不谈论闭包,一般的,从在全局执行环境创建一个函数开始。
在创建一个函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在函数内部的[[Scope]]
。
然后执行流进入这个函数,函数的执行环境被压入环境栈中,此函数执行环境的活动对象作为变量对象被创建并推入执行环境作用域链的前端。
对这个例子中的函数而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。
无论在什么时候在函数中访问变量时,会从作用域链搜索变量名。
一般情况下,函数执行完,局部活动对象就会被销毁,内存中仅有全局作用域(里边只有全局执行环境的变量对象)。
以下面这段代码为例:
function compare (value1, value2) {
//创建一个预先包含全局变量对象的作用域链,保存在[[Scope]]
if (value1 < value2) {
//访问函数变量时,即在代码最后一条语句执行过程中,会从作用域链前端开始搜索变量名
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
//执行流进入函数时,compare的执行环境压入环境栈
//compare执行环境的活动对象作为变量对象接到作用域链的前端
//函数执行完,compare执行环境弹出栈,compare活动对象销毁
如图,作用域链从0开始向后查找:
2.产生闭包的情况下
如下是一个以属性名作为参数,按其属性的值对数据进行排序的函数:
function createComparisonFunction(propertyName) {
return function(object1,object2){ //返回一个匿名函数
var value1=object1[propertyName];
var value2=object2[propertyName];
if(value1<value2){
return -1;
} else if (value1>value2){
return 1;
} else {
return 0;
}
};
}
var data=[{name:"Zachary",age:28},{name:"Nicholas",age:29}];
data.sort(createComparisonFunction("name"));
console.log(data[0]); //Object {name: "Nicholas", age: 29}
data.sort(createComparisonFunction("age"));
console.log(data[0]); //Object {name: "Zachary", age: 28}
createComparisonFunction()
函数和返回的匿名函数的作用域链如下图所示:
在匿名函数从
createComparisonFunction()
中被返回后,它的作用域链被初始化为包含createComparisonFunction()
函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction()
中定义的所有变量。
更为重要的是:
createComparisonFunction()
函数在执行完毕后,其他活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。
当createComparisonFunction()
函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中;直到匿名函数被销毁,createComparisonFunction()
的活动对象才会被销毁。
例如以下代码,返回的匿名函数被保存在变量compareNames
中,通过将compareNames
设置为null
来解除对匿名函数的引用,解除引用之后垃圾回收例程将会清除该匿名函数,随之该匿名函数的作用域链也会被销毁,则其作用域链上的其他作用域也会安全的销毁(全局作用域除外)。
var compareNames = createComparisonFunction("name");
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
compareNames = null;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。