闭包里的微观世界

本文旨在解释闭包里的微观世界javascript

内容包含:值类型做用域闭包html

JS当中全部的function都是闭包,通常说来,嵌套的function的闭包性更强。这也是咱们平时接触和研究比较多的地方。java

在进入本文的核心部分之前,首先来理解几个概念:闭包

  • 值类型函数

    声明一个值类型变量,编译器会在栈上分配一个空间,这个空间对应着该值的类型变量,空间存储的就是这个变量的值。存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。spa

  • 引用类型.net

    引用类型的实例分配在堆(heap)上,新建一个引用类型的实例,获得的变量值对应的是该实例的内存分配地址。存储在堆(heap)中的对象,也就是说,存储在变量中的值是一个指针(point),其指向存储对象的位置。3d

    javascript//值类型
    var a="xl";
    var b=a;
    a="XL";
    console.log(b); //输出  "xl"
    
    //引用类型
    var a={name:"xl"};
    var b=a;
    a.name="XL";
    console.log(b.name);//输出 "XL"

区别就是值类型变量是能够直接访问栈(stack)中的值:指针

  • 在第一段代码中,将变量"a"赋值给"b",至关于在stack中也为"b"开辟了一个存储其值的空间,与存储变量"a"的存储空间是相互独立的,所以修改"a"的值,不会影响到“b”的值。
  • 在第二段代码中,"a","b"都得到的是对于存储在heap当中实例的引用,当“a”对其进行修改的时候,“b”的引用也会受到影响。

接下来的内容就是关于闭包的微观世界code

javascriptfunction a(){
        var i=0;
        function b(){
            console.log(++i);
        }
        return b;
    }

    var c=a(); //函数a执行后返回函数b,并将函数b赋给c
    c();//输出 1

原本这个地方变量i是定义在函数a中,并不能被函数a的外部所访问,可是这个地方由于在a中定义了一个函数b,函数b中有对变量i的引用,所以当b被a返回后,变量c得到了对函数a中函数b的引用,所以i不会被GC回收,而是存在内存当中。

当在一个函数a里面定义另一个函数b,函数b有对函数a中变量的引用,当函数a执行并返回函数b,将b赋给变量c时,这样就存在相互之间的引用关系,并造成了你们常常见到的闭包

咱们进一步的分析:这一部分的内容包含了做用域做用域链部分的内容.

依然拿上面的例子来分析:

  • 当定义函数a的时候,js解释器会将函数a的做用域链(scope chain)设置为定义a时所在的“环境”,若是a是一个全局函数,那么scope chain中只有window对象。

  • 当执行函数a的时候,a会进入相应的执行环境(excution context).

  • 在建立执行环境的过程当中,首先会为a添加scope属性,即a的做用域,其值就为第一步的scope chain.即a.scope=a的做用域链。

  • 而后执行环境会建立一个活动对象(call object).活动对象也是一个拥有属性的对象。但它不具备原型并且不能直接经过javascript代码访问。建立完活动对象后,把活动对象添加到a的做用域的最顶端,此时a的做用域链包含2个对象:a的活动对象和window对象。

  • 下一步是在活动对象上添加一个arguments属性,它保存着调用a时所传递的参数。最后把全部函数a的形参以及定义的内部函数b添加到a的活动对象上。在这一步中,完成了函数b的定义,正如第一步,函数b的做用域链被设置为b被定义时所处的环境,即a的做用域
    到此,整个函数a从定义到执行的过程就完成了。此时a返回函数b的引用给c,又函数b的做用域链包含了对函数a的活动对象的引用,也就是说b能够访问到a中定义的全部变量和函数。函数b被c引用,函数b又依赖函数的a,所以函数a在返回的时候不会被gc收回。

  • 当函数b执行的时候,一样会按上述步骤同样。执行时b的做用域里包含了3个对象:{b的活动对象}、{a的活动对象}、{window对象}

下面用2张图来表示整个过程:

图一展现了函数a定义过程是如何建立做用域链的

图片描述

图二展现了函数a执行过程产生的活动对象(call object)

图片描述

在这其中有个很是重要的内容就是函数的做用域是在定义函数的时候就已经肯定,而不是在执行的时候肯定。

具体内容参见:鸟哥:Javascript做用域和做用域链

再来看看咱们在平时常常遇到的一段代码:

javascriptHTML部分:
        <div id="example">
            <span>1</span>
            <span>2</span>
            <span>3</span>
        </div>

    JS:

    var spanArr=document.getElementById("example").getElementsByTagName("span");
    for(var i=0;i<3;i++){
        spanArr[i].onclick=function(){
            console.log(i);
        }
    }
    //无论点击哪一个<span>都会输出3
    //这是由于在内部的匿名函数中i是对于外部的i的引用。当for循环结束之后,i的值变为了3.那么匿名函数相应得到的引用值夜都变为了3.因此最后无论点击哪一个<span>最后都会输出3.
    //因此遇到这种状况的时候通常处理方法是
    1.将变量i保存在每一个span对象上。
    for(var i=0;i<3;i++){
        spanArr[i].i=i;
        spanArr[i].onclick=function(){
            console.log(i);
        }
    }
    2.加一层闭包
    for(var i=0;i<3;i++){
        (function(i){
            spanArr[i].onclick=function(){
                console.log(i);
            }
        })(i)
    }
    //固然还有其余的方法,这里很少述。

参考文章:

  1. 理解javascript的做用域和做用域链
  2. javascript闭包深刻理解
  3. 理解javascript闭包
相关文章
相关标签/搜索