关于javascript闭包 javascript作用域链

javascript / 2012年12月12日 17时21分 / 8824人浏览
闭包:这是个非常古老的术语,是指函数变量可以被隐藏于作用域链之内,因此看起来是函数将变量“包裹”了起来。

首先了解一下javascript的 作用域链

Javascript是基于词法的作用域语言,通过阅读包括变量定义在内的数行源码就能知道变量的作用域。全局变量在程序中始终都是有定义的。局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的。 每一段javascript代码(全局代码或者函数)都有一个与之关联的作用域链(scope chain)。这个作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。当javascript需要查找变量x的值的时候(这个过程叫做变量解析),它会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,这会直接使用这个属性的值,如果第一个对象中不存在名为x的属性,javascript会继续查找链上下一个对象。如果第二个对象依然没有名为x的属性,则会继续查找下一个对象,以此类推。如果作用域链上没有任何一个对象含属性x,那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误(referenceError)异常。 在javascript的最顶层代码中(也就是不包含在任何函数定义内的代码),作用域链由一个全局对象组成。 在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。 在一个嵌套的函数体内,作用域链上之上有三个对象。理解对象链的创建规则是非常重要的。当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建一个对象来存储它的局部变量,并将这个对象添加到保存的那个作用域链上,同时创建一个新的更长的表示函数的作用域“链”。对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数每次定义的时候都有微妙的差别—在每次调用外部函数时,内部函数的代码都是相同的,而关联这段代码的作用域链也不相同。  

实现闭包

如果理解了词法作用域的规则,就能很容易的理解闭包:函数定义时的作用域链到函数执行时依然有效。然而,很多程序员觉得闭包非常难理解,以为他们在深入学习闭包的实现细节时将自己搞的晕头转向。他们觉得在外部函数中定义的局部变量在函数返回后就不存在了(很多人认为函数执行结束后,与之相关的作用域链不存在了,但在javascript中并非如此), 那么嵌套函数如何能调用不存在的作用域链呢?如果你想搞清楚这个问题,你需要更深入的理解类似c语言这种更底层的编程语言,并了解基于栈的cpu架构:如果一个函数的局部变量定义在cpu的栈中那么当函数返回时他们的确就不存在了。   但是在javascript中,我们将作用域链描述为一个对象列表,不是绑定的栈。每次调用javascript函数的时候,都会为之创建一个新的对象来保存局部变量,把这个对象添加至作用域链中。当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数,也就没有其他的引用指向这个绑定的对象,它就会被当垃圾回收掉。如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。但如果这些嵌套的函数对象在外部函数中保存下来,那么他们也会和所指向的变量绑定对象一样当作垃圾回收。但是如果这个函数定义了嵌套函数,并将他作为返回值返回或者存储在某处的属性里,这时候就会有一个外部引用指向这个嵌套的函数。它就不会被当作垃圾回收,并且它所指向的变量绑定对象也不会被当垃圾回收。   上面解释了javascript闭包和垃圾回收的关系,如果使用不慎,闭包很容易造成“循环引用”,当DOM对象和javascript对象之间存在循环引用时,在某些浏览器下会造成内存泄漏。