javascript闭包运行原理,javascript的闭包
最近在网上查阅了很多Javascript闭包相关的资料,大部分都很学术很专业。对于初学者来说,不理解封闭包,连文字叙述都很难理解。写这篇文章的目的是用最流行的话来揭示Javascript闭包的真实面目。
-
1.什么是终结?
“官方”的解释是,闭包是一个表达式(通常是一个函数),有很多变量和这些变量绑定的环境,所以这些变量也是表达式的一部分。
我相信很少有人能直接理解这句话,因为他描述的太学术了。事实上,这句话很流行:JavaScript中所有函数都是一个闭包。不过一般来说,嵌套函数生成的闭包更强大,也就是我们大多数时候所说的“闭包”。请看下面的代码:
函数a() {
var I=0;
函数b() {
警报(一);
}
返回b;
}
var c=a();
c();这种代码有两个特点:
函数b嵌套在函数a内部;
a函数返回b函数。
参考关系如图所示:
这样在执行var c=a()后,变量C实际上指向了函数B,变量I在B中使用,执行C()后会弹出一个窗口显示I的值(第一次为1)。这段代码实际上创建了一个闭包。为什么?因为函数A外的变量C引用了函数A内的函数B,即:
当函数A的内部函数B被函数A外部的变量引用时,就产生了我们通常所说的“闭包”。
让我们更彻底。所谓“闭包”,就是在构造函数体中定义另一个函数作为目标对象的方法函数,这个对象的方法函数反过来引用外部函数体中的临时变量。只要目标对象能够在它的生命周期中一直保持它的方法,这就使得间接保持原始构造函数体使用的临时变量值成为可能。虽然最初的构造函数调用已经结束,临时变量的名字也消失了,但是变量的值总是可以在目标对象的方法中被引用,并且该值只能被这个方法访问。即使再次调用同一个构造函数,也只会生成新的对象和方法。新的临时变量只对应新的值,与上次调用的值无关。
为了更深入地理解闭包,让我们继续探索闭包的功能和效果。
二、封闭的作用和效果是什么?
简而言之,闭包的作用就是在A被执行并返回后,使得Javascript的垃圾收集机制GC无法回收A所占用的资源,因为A的内部函数B的执行依赖于A中的变量,这是对闭包作用的一个非常直白的描述。很不专业,不严谨,但大概就是这个意思。理解闭包需要一个渐进的过程。
在上面的例子中,由于闭包的存在,A中的I在函数A返回后总是存在的,这样每次执行C()时,I就是加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添加一个scope属性,即A的作用域,其值为步骤1中的作用域链。也就是a.scope=a的作用域链.
那么执行环境将创建一个调用对象。activity对象也是一个具有属性的对象,但是它没有原型,不能通过JavaScript代码直接访问。创建活动对象后,将活动对象添加到A的作用域链的顶端,此时A的作用域链包含两个对象:A的活动对象和窗口对象。
下一步是向活动对象添加arguments属性,该属性保存调用函数a时传递的参数。
最后将函数A的所有形参和内部函数B的引用添加到A的活动对象中,这一步完成了函数B的定义,于是如步骤3一样,将函数B的作用域链设置为B定义的环境,即A的作用域。
至此,整个功能A从定义到执行的步骤完成。这时A返回函数B对C的引用,函数B的作用域链包含了对函数A的活动对象的引用,这意味着B可以访问A中定义的所有变量和函数,函数B被C引用,函数B依赖于函数A,所以函数A返回后不会被GC回收。
当执行功能b时,它会像上面的步骤一样。因此,B在执行过程中的作用域链包含三个对象:B的活动对象、A的活动对象和窗口对象,如下图所示:
如图所示,访问函数B中的变量时,搜索顺序为:
先搜索自己的活动对象,如果存在就返回;如果不存在,它会继续搜索函数A的活动对象,依次搜索,直到找到为止。
如果函数B中有原型对象,在寻找自己的活动对象后,再寻找自己的原型对象,然后继续寻找。这就是Javascript中的变量查找机制。
如果在整个作用域链中找不到它,则返回undefined。
总结,本段提到了两个重要的词:函数的定义和执行。本文中提到的函数的范围是在定义函数时确定的,而不是在执行函数时确定的(参见步骤1和3)。用一段代码来说明这个问题:
函数f(x) {
var g=function(){ return x;}
返回g;
}
var h=f(1);
alert(h());这段代码中的变量h指向f中的匿名函数(由g返回)。
假设函数H的作用域是通过执行alert(h())确定的,那么H的作用域链就是:H的活动对象——alert的活动对象——窗口对象。
假设函数H的作用域其实在定义的时候就已经确定了,也就是说H指向的匿名函数在定义的时候就已经确定了它的作用域。那么在执行的时候,H的作用域链就是:H的活动对象——F的活动对象——窗口对象。
如果第一个假设成立,则输出值是未定义的;如果第二个假设成立,则输出值为1。
结果证明第二个假设是正确的,说明在定义函数的时候,函数的值域确实是确定的。
四。闭包的应用场景
函数中的安全变量。以最初的例子为例。在函数A中,I只能被函数B访问,不能被其他途径访问,从而保护了I的安全性。
在内存中维护一个变量。和前面的例子一样,由于闭包,函数A中的I始终存在于内存中,所以每执行一次C()I就会加1。
通过保护变量的安全实现JS私有属性和私有方法(不能外部访问)推荐阅读:http://javascript.crockford.com/private.html
不能在构造函数外部访问私有属性和方法。
函数构造函数(.) {
var that=this
var membername=value
函数成员名(.) {.}
}以上三点是闭包最基本的应用场景,很多经典案例都源于此。
Javascript的垃圾收集机制
在Javascript中,如果一个对象不再被引用,它将被GC回收。如果两个对象互相引用,并且不再被第三个对象引用,那么互相引用的两个对象也会被回收。因为函数A被B引用,B被A外的C引用,这就是函数A执行后不会被回收的原因。
不及物动词结论。
理解JavaScript的闭包是进阶JS程序员的必经之路。只有理解了它的解释和运行机制,才能写出更安全、更优雅的代码。如果您对本文有任何建议和疑问,请留言。请转载著名出处。
详细的参考资料来源:http://www.jb51.net/article/18303.htm
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。