闭包

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的活动对象

嵌套函数的作用域链
函数调用作用域链=函数定义作用域链+活动对象(作用域链的本质是一个指向变量对象的指针列表)。
1. 每次调用外部函数的时候,都会生成一个新的函数调用作用域链,因为每次调用外部函数都会生成一个全新的活动对象。
2. 而每次调用外部函数的时候,内部函数又会重新定义一遍,这个不难理解,因为函数内执行了函数定义的代码。
3. 内部函数定义时会将包含函数(即外部函数)的活动对象添加到它的作用域链(指函数定义作用域链)中。
4. 在函数嵌套中,外部函数执行结束(或者说调用结束)后,内部函数(即闭包)的定义作用域链就确定下来了。注意理解这一点很重要。

  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闭包更改了这个节点对象,其他闭包中能够访问到的这个节点上的任何变量也会跟着改变,受到影响。

原文地址:https://www.cnblogs.com/iamswf/p/4667649.html