作用域和闭包


函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性称作「闭包」。从技术角度讲,所有的 JavaScript 函数都是闭包:它们都是对象,都关联到作用域链。

理解闭包先要了解嵌套函数的词法作用域规则:函数定义时的作用域链在函数执行时依然有效。为了更直观的看效果,我们来看一下下面两段代码的对比:

两段代码输出结果一致,可见绑定到嵌套函数 f() 的作用域链是在定义时创建的,不管在何时何地执行 f(),这种绑定都是有效的。

实现闭包

我们将作用域链描述为一个对象列表,每次调用 JavaScript 函数时,都会为之创建一个新的对象用来保存局部变量,并把这个对象保存到作用域链中,当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套函数,也没有其他引用值指向这个绑定对象,它就会被当做垃圾回收掉。如果定义了嵌套函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象,如果这些嵌套的函数对象在外部函数中保存下来,那么它们也会和所指向的变量绑定对象一样当做垃圾回收;如果嵌套函数作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数,它就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收,这种情况下就行成了闭包(严格意义上的闭包)。

了解了闭包及其实现原理,我们来看一些闭包的使用场景。

在自定义函数属性那里,我们实现了一个函数调用次数计数器,这种做法有一个问题,就是恶意代码可能将计数器重置或者把一个非整数赋值给它。我们可以通过「闭包」将这个属性转换为私有状态:

在同一个外部函数内定义的多个嵌套函数都可以访问 counter 这样的私有变量,并且这些嵌套函数共享一个作用域链:

上述代码中 count() 函数用于计数,reset() 用于重置计数器,同一个函数对象共享一个作用域链,不同对象间作用域链是独立的且不会相互影响。

还可以通过存取器方法 gettersetter 对上述代码进行重写:

下面我们来实现一个给函数对象添加私有属性的通用方法:

通过上面的示例,我们也可以得出结论:在同一个作用域链中定义两个闭包,这两个闭包会共享同样的变量。这一点很重要。

编写闭包的时候还需要注意,this 是 JavaScript 的关键字而不是变量,每个函数都有自己的 this 值,在闭包里不能直接访问外部函数的 this,除非手动将其赋值给一个变量,然后在闭包中访问这个变量。

另外,arguements 也是类似,每个函数在调用时都会声明自己的 arguements,因此在闭包中也不能访问外部函数的 arguments。除非将其赋值给一个变量,然后在闭包中访问这个变量。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 作为命名空间的函数

>> 下一篇: 函数属性、方法和构造函数