function createFunctions(){
var result = new Array();
for(var i = 0; i < 10; i++){
result[i] = function(){return i};
}
return result;
}
var r = createFunctions();
console.log(r[0]());
function createFunctions1(){
var result = new Array();
function a(num){
return function(){
return num;
};
}
for(var i = 0; i < 10; i++){
result[i] = a(i);
}
return result;
}
var r1 = createFunctions1();
console.log(r1[0]());
调用外部函数时会定义内部函数,而定义内部函数时会将外部函数的活动对象添加到它的作用域链中。因此调用a函数结束时,会把a函数的活动对象添加到返回的匿名函数的作用域链中,第一次调用a函数结束时,num变量的值为0,第二次为1,以此类推。调用10次a函数结束时,会生成10个不同的活动对象,也会返回10个匿名函数,这10个匿名函数有10条作用域链:
全局对象
|
第一次调用createFunction1的活动对象
| |
第1次调用a的活动对象 ... 第10次调用a的活动对象
createFuctions1函数执行时,会执行“立即执行的函数a”,循环10次,就会执行函数a10次,当然也a函数也会返回10次。注意,每次a函数返回时a函数的活动对象都会确定下来(确定下来的意思是这次调用a函数的活动对象不再变化,这一点很重要)。
每次执行a函数时,又会定义内部匿名函数。先说说第一次调用a函数,定义第一个匿名函数时,该匿名函数的作用域链为:global对象--->createFunctions1函数的活动对象(节点2,因为该函数还未返回,所以作用域链上的这个节点还在“晃动”)--->a函数的活动对象(节点3,这个对象里面包括num)。在a函数第一次返回时,上述作用域链上的节点3确定下来,但是节点2还在“晃动”。第二次调用a函数时,又会定义一个新的匿名函数,又会有新的活动对象。第二次调用a函数返回时返回的匿名函数的作用域链为:global对象--->createFunctions1函数的活动对象(节点2,因为该函数依然没有返回,所以作用域链上的这个节点依然在“晃动”)--->a函数的活动对象(节点3,这个对象里面包括num)。以此类推,还有生成剩下的8条作用域链,最有一条作用域链确定下来后,createFunctions1函数终于返回了,这时候这10条作用域链上的节点2都不再“晃动”,这时节点2上的i变量也变成了10。
所以,引用犀牛书上的一句话,关联到闭包上的作用域链都是“活动的”,记住这一点很重要。It is important to remember that the scope chain associated with a closure is "live".
再次理解闭包:
假设一个函数内部定义了另外一个函数,那么内部函数的定义将发生在外部函数调用的时候,而这个时候外部函数的“调用作用域链”等于“定义作用域链”添加上外部函数的“活动对象”,内部函数的“定义作用域链”就是外部函数调用时候的作用域链,在外部函数调用返回之前,外部函数的“活动对象”一直处于活动状态,即可能变化,同时也会导致内部函数的“定义作用域链”也没有确定。当外部函数调用返回时,内部函数的“定义作用域链”也确定下来。总之,只有外部函数调用返回后,内部函数的“定义作用域链”才会确定。所以,对于闭包的那个经典问题,如果想让某个变量的“即时值”能够成功被内部函数访问,可以通过添加一层立即执行函数来解决,因为立即执行函数会马上返回,所以这个立即执行函数的活动对象会立刻稳定下来,成为作用域链上的一个稳定节点。
再次理解闭包:
如果在一个外部函数内部定义了另外的多个函数,那么当外部函数调用结束时内部函数的也定义完成。假设外部函数内有一个局部变量n,那么内部定义的这几个函数(闭包)就都有访问该变量n的权利。如果某一个内部函数对n做了修改,那么将会影响到其他函数,因为在同一次外部函数调用后返回的这些函数是共享这个变量n的。看下面的例子:
var nAdd; function t(){ var n = 99; nAdd = function () { n++; } return function () { console.log(n); } } var a = t(); var b = t(); nAdd(); a();//99 b();//100
在上面的例子中,var a = t()会调用一次外部函数t,调用结束后函数nAdd()和函数a()的作用域链中会共享一个变量n。如下图所示:
----------------------------------------------------------------------------------------------------------------------------------------
全局作用域
|
t()函数调用的活动对象,变量n就存在该活动对象内
| |
a()函数调用的活动对象 nAdd()函数调用的活动对象(上面的例子程序中没有这个活动对象,因为还没有调用nAdd()函数该函数已经被重写了)
----------------------------------------------------------------------------------------------------------------------------------------
var b = t()会再次调用外部函数t,调用结束后函数nAdd被重写了一遍,重写后nAdd()函数中还是能够访问变量n,只是这个n存在于新的函数调用产生的作用域链上的节点。如下图所示:
----------------------------------------------------------------------------------------------------------------------------------------
全局作用域
|
t()函数调用的活动对象,变量n就存在该活动对象内
| |
b()函数调用的活动对象 nAdd()函数调用的活动对象
----------------------------------------------------------------------------------------------------------------------------------------由于重写后的nAdd()函数和b()函数共享一个作用域链上的节点,因此任何一个函数或者说闭包更改了这个共享节点对象,其他函数或者说闭包也会受到影响跟着改变。因此上面的运行结果是99, 100。
所以,现在理解闭包因该注意两点:
1. 调用外部函数返回后内部函数的定义作用域链就确定下来,不在晃动。
2. 所以一次外部函数的调用返回多个闭包,那么这多个闭包会共享一个作用域链上的节点,这个节点就是外部函数调用结束后的活动对象。任何一个i闭包更改了这个节点对象,其他闭包中能够访问到的这个节点上的任何变量也会跟着改变,受到影响。