宝哥软件园

JavaScript闭包

编辑:宝哥软件园 来源:互联网 时间:2021-10-26

一、什么是闭包?

的官方解释是闭包是一个有很多变量的表达式(通常是一个函数),环境与这些变量绑定,所以这些变量也是表达式的一部分。我相信很少有人能直接理解这句话,因为他的描述太学术了。其实这句话是:JavaScript中的所有函数都是一个闭包。一般来说,嵌套函数产生的闭包更强大,大部分时候就是我们所说的“闭包”。请看下面的代码:函数a(){ var I=0;函数b(){ alert(I);}返回b;} var c=a();c();这段代码有两个特点:函数b嵌套在函数a内部;a函数返回b函数。参考关系如下:jsclosure

这样,在执行var c=a()之后,变量c实际上指向函数b,其中使用了变量I。执行c()后,会弹出一个窗口显示I的值(第一次是1)。这段代码实际上创建了一个闭包。为什么呢?因为函数A外的变量C指的是函数A内的函数B,也就是说,当函数A的内部函数B被函数A外的变量引用时,就产生了所谓的“闭包”。让我们说得更透彻些。所谓“闭包”,就是在构造函数体中定义另一个函数作为目标对象的方法函数,这个对象的方法函数反过来引用外部函数体中的临时变量。这使得可以间接维护原始构造函数使用的临时变量值,只要目标对象可以在其生存期内始终保持其方法。虽然初始构造函数调用已经结束,临时变量的名称已经消失,但是变量的值始终可以在目标对象的方法中引用,并且该值只能由该方法访问。即使再次调用同一个构造函数,也只会生成新的对象和方法,新的临时变量只对应新的值,与上次调用的值无关。为了更深入地理解闭包,让我们继续探讨闭包的功能和作用。

二、闭包有什么作用和效果?

简单来说,闭包的作用是A完成执行并返回后,闭包使得Javascript的垃圾收集机制GC不回收A占用的资源,因为A的内部函数B的执行依赖于A中的变量.这是对闭包功能的非常直接的描述,这是不专业和不精确的,但它可能意味着这一点。理解闭包需要一个渐进的过程。在上面的例子中,由于闭包的存在,函数a返回后我总是存在于a中,所以每次执行c()时,我都是alert加1后的值。让我们想象另一种情况。如果A不返回函数B,情况就完全不同了。因为A执行后,B不返回A的外部世界,只被A引用,此时A也只会被B引用。所以函数A和B相互引用,不受外界干扰(被外界引用),函数A和B会被GC回收。(后面会详细介绍Javascript的垃圾收集机制)

三、闭包的微观世界

如果我们想更多地了解闭包以及函数A和嵌套函数B之间的关系,还需要引入其他几个概念:函数执行上下文、调用对象、作用域和作用域链。以函数A从定义到执行的过程为例来说明这些概念。定义函数A时,js解释器会将函数A的作用域链设置为定义A时A所在的“环境”,如果A是全局函数,则作用域链中只有一个窗口对象。当函数a被执行时,a将进入相应的执行上下文。在创建执行环境的过程中,首先给A增加一个作用域属性,即A的作用域,其值在步骤1中为作用域链。也就是a.scope=a的范围链.然后,执行环境创建一个调用对象。活动也是一个带有属性的对象,但是它没有原型,不能通过JavaScript代码直接访问。创建活动对象后,将活动对象添加到A的范围链的顶部。此时,的范围链包含两个对象:的活动对象和窗口对象。下一步是在活动对象上添加一个arguments属性,保存调用函数A时传递的参数,最后将函数A的所有形式参数和函数B的内部引用都添加到A的活动对象中,这一步完成了函数B的定义,所以和第三步一样,将函数B的作用域链设置到B定义的环境中,也就是A的作用域中,此时就完成了整个函数A从定义到执行的步骤。此时a返回函数b对c的引用,函数b的作用域链包含函数a的活动对象的引用,这意味着b可以访问a中定义的所有变量和函数.函数b被c引用,依赖于函数a,所以函数a返回后不会被GC回收。当执行函数b时,它将与上述步骤相同。

因此,b的范围链包含三个对象:b的活动对象、a的活动对象和窗口对象,如下图所示:/upload/20090527123259248.jpg

如图,在函数B中访问一个变量时,搜索顺序是:先搜索自己的活动对象,如果存在,返回,如果不存在,继续搜索函数A的活动对象,依次搜索,直到找到为止。如果函数b中有原型对象,在找到自己的活动对象后找到自己的原型对象,然后继续寻找。这是Javascript中的变量搜索机制。如果在整个范围链中找不到它,它将返回undefined。总之,这一段提到了两个重要的词:函数的定义和执行。在本文中,函数的范围是在定义函数时确定的,而不是在执行函数时确定的(参见步骤1和3)。用一段代码解释这个问题:函数f(x){ var g=function(){ return x;}返回g;} var h=f(1);警报(h());这段代码中的变量h指向f中的匿名函数(由g返回)。假设函数h的作用域是通过执行alert(h())确定的,那么h的作用域链就是:h的活动对象-alert-the-window对象的活动对象。假设函数h的作用域是在定义的时候确定的,也就是说h指向的匿名函数在定义的时候已经确定了它的作用域。在执行的时候,h的作用域链是:h的活动对象-f的活动对象-窗口对象。如果第一个假设成立,则输出值未定义;如果第二个假设成立,输出值为1。结果证明第二个假设是正确的,这意味着函数的范围是在定义函数时确定的。

四、闭包的应用场景

保护函数中的变量。以初始例子为例,函数A中的I只能被函数B访问,而不能被其他手段访问,从而保护了I的安全性,在内存中维护一个变量。和前面的例子一样,因为闭包的原因,函数A中的I总是存在于内存中,所以每次执行C()时,我都会被加1。通过保护变量的安全性来实现JS私有属性和方法(不能从外部访问)。推荐阅读:http://javascript.crockford.com/private.html私有属性和方法是不可访问的函数Constructor(.){ var=this;var membername=value函数成员名(.) {.}}以上三点是闭包最基本的应用场景,很多经典案例都源于此。

五、Javascript的垃圾回收机制

在Javascript中,如果一个对象不再被引用,它将被GC回收。如果两个对象相互引用,并且不再被第三个引用,那么这两个相互引用的对象也将被回收。因为函数A被B引用,而B在A之外被C引用,这就是函数A执行后不会被回收的原因。00-1010理解JavaScript的闭包是高级JS程序员的必经之路。只有了解它的解释和运行机制,才能写出更安全、更优雅的代码。如果您对本文有任何建议或问题,请留言。请转载著名出处。

更多资讯
游戏推荐
更多+