闭包的字面意思是:有权访问另一个函数作用域中的变量的函数。
在javascript中,创建一个闭包最常见的方式就是在一个函数的内部创建另一个函数。
function test(property){
return function(object1,object2){
var value1 = object1[property];
var value2 = object2[property];
if(value2 < value1){
return -1;
}else{
return 0;
}
};
}
如上面的函数,代码块中加粗的两行代码访问了外部函数的变量property,即使这个内部函数在其他地方被调用,它仍然可以访问外部函数中property这个变量,此时这个内部函数就形成了闭包。
具体了解闭包,要从作用域链入手。
当一个函数被调用时,会创建一个执行环境以及相应的作用域链。
执行环境确定了变量或者函数有权访问其它数据的一个范围。每一个执行环境都会有一个与之相应的变量对象,环境中定义的所有变量和函数都保存在这个对象总。全局环境是最外围的一个环境,在web浏览器中,全局执行环境就是window对象。当这个执行环境中的所有代码执行完毕时,该环境就会被销毁。
当代码在环境中执行时,会创建变量对象的一个作用域链,作用域链的主要作用是保证对执行环境有权访问的所有变量和函数的有序执行。什么意思呢?看下面的代码:
1 var color = 'blue';
2 function changeColor(){
3 if(color === 'blue'){
4 color = 'red';
5 }else{
6 color = 'blue';
7 }
8 }
9 changeColor();
10 alert(color);
当代码执行到changeColor函数时,会创建一个执行环境。该环境会创建变量对象的一个作用域链。作用域链的最前端,始终都是当前执行代码所在环境的变量对象,如果这个环境是函数,则arguments对象是活动对象,下一个变量对象就来自于包含该函数的外部环境,再下一个来自于下一个包含的环境,以此类推。针对于changeColor函数的作用域链来讲,作用域链的最前端是它自己的变量对象,即arguments对象,下一个变量对象就来自于包含该函数的外部环境,即全局环境中的变量对象,再往后就没有了。因此changeColor函数之所以能够访问全局变量color,是因为变量color在作用域链上,在全局环境中的变量对象里。
再比如:
1 var color = 'blue';
2 function changeColor(){
3 var anotherColor = 'red';
4 function swapColors(){
5 var tempColor = anotherColor;
6 anotherColor = color;
7 }
8 swapColors();
9 }
10 changeColor();
在上述代码中,共有三个执行环境,全局环境,changeColor的执行环境和swapColors的执行环境。swapColors函数可以访问changeColor中的变量和全局环境中的变量,但是changeColor函数不能访问swapColors中的变量,因为这些变量不在changeColor的作用域链上。
内部环境可以通过作用域链方位所有的外部环境,但是外部环境不能访问内部环境中任何变量和函数。无论什么时候在函数中访问一个变量,都会从作用域链中从内部执行环境开始向外部执行环境搜索具有相应名字的变量,如果没有搜索到就默认为是全局变量。当幻术执行完毕后,函数内的活动对象就会被销毁,内存中只保存全局环境中的变量对象。但是闭包的情况不太一样。
在一个函数内部定义的函数会将外部函数的活动对象添加到它的作用域链中,这样在其它地方访问内部函数时,该内部函数可以访问外部函数中所有的变量。因此当外部函数执行结束后,执行环境被销毁,但其活动对象仍然存在在内存中,直到匿名函数被销毁。也是因此,闭包比其他函数占用更多的内存。