资讯专栏INFORMATION COLUMN

闭包里的微观世界

Miyang / 2161人阅读

摘要:本文旨在解释闭包里的微观世界。创建完活动对象后,把活动对象添加到的作用域的最顶端,此时的作用域链包含个对象的活动对象和对象。

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

内容包含:值类型作用域闭包

  

JS当中所有的function都是闭包,一般说来,嵌套的function的闭包性更强。这也是我们平时接触和研究比较多的地方。

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

值类型

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

引用类型

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

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”的引用也会受到影响。

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

javascript    function 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作用域和作用域链

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

javascript    HTML部分:
        
1 2 3
JS: var spanArr=document.getElementById("example").getElementsByTagName("span"); for(var i=0;i<3;i++){ spanArr[i].onclick=function(){ console.log(i); } } //不管点击哪个都会输出3 //这是因为在内部的匿名函数中i是对于外部的i的引用。当for循环结束以后,i的值变为了3.那么匿名函数相应获得的引用值夜都变为了3.所以最后不管点击哪个最后都会输出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) } //当然还有其他的方法,这里不多述。

参考文章:

理解javascript的作用域和作用域链

javascript闭包深入理解

理解javascript闭包

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85831.html

相关文章

  • 理解JavaScript的核心知识点:作用域

    摘要:也毫不例外,但在中作用域的特性与其他高级语言稍有不同,这是很多学习者久久难以理清的一个核心知识点。主要使用的是函数作用域。 关于作用域:About Scope 作用域是程序设计里的基础特性,是作用域使得程序运行时可以使用变量存储值、记录和改变程序的状态。JavaScript 也毫不例外,但在 JavaScript 中作用域的特性与其他高级语言稍有不同,这是很多学习者久久难以理清的一个核...

    HelKyle 评论0 收藏0
  • 理解javascript核心知识点

    摘要:作用域链的作用就是做标示符解析。事件循环还有个明显的特点单线程。早期都是用作开发,单线程可以比较好当规避同步问题,降低了开发门槛。单线程需要解决的是效率问题,里的解决思想是异步非阻塞。 0、前言 本人在大学时非常痴迷java,认为java就是世界上最好的语言,偶尔在项目中会用到一些javascript,但基本没放在眼里。较全面的接触javascript是在实习的时候,通过这次的了解发现...

    laznrbfe 评论0 收藏0
  • 【进阶3-5期】深度解析 new 原理及模拟实现

    摘要:使用指定的参数调用构造函数,并将绑定到新创建的对象。由构造函数返回的对象就是表达式的结果。情况返回以外的基本类型实例中只能访问到构造函数中的属性,和情况完全相反,结果相当于没有返回值。 定义 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。 ——(来自于MDN) 举个栗子 function Car(color) { this.color = co...

    Baaaan 评论0 收藏0
  • 【译】闭包并不神秘

    摘要:下面我们就初步尝试一下闭包现在来看一下发生了什么。于是,这种结构就被称作闭包。这就是闭包强大的地方。例如,如果我们可以在我们的计数器里面加一个名字我们可以往闭包里传一个参数可以看出来,在实现过程中不仅能记住局部变量,也记住了传进来的变量。 计数器 首先,从一个计数器开始。 var counter = 0; function increment() { counter = cou...

    sevi_stuo 评论0 收藏0
  • 谈谈我所理解的闭包,js、php、golang里的closure

    摘要:当初看这个解释有点懵逼,理解成闭包就是函数中的函数了。里的闭包最近不满足于只干前端的活,开始用起了。里的闭包最近在学习语言,让我们来看一下语言里的闭包。在中,闭包特指将函数作为值返回的情况,被返回的函数引用了生成它的母函数中的变量。 本人开始接触编程是从js开始的,当时网上很多人说闭包是难点,各种地方对闭包的解释也是千奇百怪。如今开始接触js以外的各种编程语言,发现不光是js,php、...

    betacat 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<