zoukankan      html  css  js  c++  java
  • 闭包

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

  • 相关阅读:
    可变性编程 不可变性编程 可变性变量 不可变性变量 并发编程 命令式编程 函数式编程
    hashable
    优先采用面向表达式编程
    内存转储文件 Memory.dmp
    windows update 文件 路径
    tmp
    查询局域网内全部电脑IP和mac地址等信息
    iptraf 网卡 ip 端口 监控 netstat 关闭端口方法
    Error 99 connecting to 192.168.3.212:6379. Cannot assign requested address
    t
  • 原文地址:https://www.cnblogs.com/iamswf/p/4667649.html
Copyright © 2011-2022 走看看