理解 JavaScript 闭包

闭包的概念

当函数 a 的内部函数 b 被函数 a 外的一个变量引用的时候,就创建了一个所谓的“闭包”。

JavaScript 中所有的 function 都是一个闭包。不过一般来说,嵌套的 function 所产生的闭包更为强大,先看个栗子:

1
2
3
4
5
6
7
8
9
function a(){
var i = 1;
function b(){
alert(i++); // b() 引用 i
}
return b; // a() 返回 b
}
var c = a(); // c 通过执行 a() 指向了 b
c(); // 相当于执行b(), 访问了 i

这段代码的特点是,函数 b 嵌套在函数 a 内部,函数 a 返回 函数 b 。

这样在执行完 var c = a(); 后,变量 c 实际上是指向了函数 b,b 中用到了变量 i ,再执行 c() 后就会弹出一个窗口显示 i 的值(第一次为1)。

闭包的作用就是在 a 执行完并返回后,闭包使得 JavaScript 的垃圾回收机制 GC 不会收回 a 所占用的资源,因为 a 的内部函数 b 的执行需要依赖 a 中的变量。

JavaScript 中的 GC 只回收不再被引用的单个对象和两个互相引用但不被第三者引用的对象。

作用域相关的概念

为了理解闭包,首先理解一下闭包的微观世界的几个概念:函数的执行环境 excution context、活动对象 call object 、作用域 scope、作用域链 scope chain ,以前面的代码中函数 a 从定义到执行的过程为例讲解:

定义 a 的时候,js 解释器会将 函数 a 的 作用域链 设置为 定义 a 时 a 所在的环境。此处 a 是一个全局函数,因此 scope chain 中只有 window 。

执行 a 的时候,a 进入相应的 执行环境 。此过程完成三件事:

一,为 a 添加 作用域 ,即 scope 属性, a.scope = a.scope chain

二,创建一个 活动对象 ,把活动对象添加到作用域链 最顶端,即此时的 scope chain 包括两个对象:a 的活动对象和 window 对象;

三,在活动对象上添加一个 arguments 属性,保存调用 a 时所传递的参数。然后把所有函数 a 的形参和内部函数 b 的引用也添加到活动对象上。同时,在这一步完成了函数 b 的定义,b 的作用域链被设置为 b 所在的环境,即 a 的作用域(第一步),然后加上 b 的活动对象(第二步)。

最终,函数 b 执行时的作用域链包含了三个对象:b 的活动对象 + (a 的活动对象 + window 对象),如下图:

b的作用域链

当在函数 b 中访问一个变量的时候,搜索顺序是(从作用域链顶部开始):

  1. 先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数 a 的活动对象,依次查找

  2. 如果函数 b 存在 prototype 原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。

这就是 JavaScript 中的变量查找机制。如果整个作用域链上都无法找到,则返回 undefined

关于作用域和作用域链的内容还可参考:JS作用域与作用域链

闭包的应用场景

三大场景:

一, 保护函数内的变量安全

以开头中的栗子来说,函数 a 中 i 只有函数 b 才能访问,而无法通过其他途径访问到,因此保护了 i 的安全性。

二,在内存中维持一个变量

由于闭包,函数 a 中 i 的一直存在于内存中,因此每次执行 c(),都会给 i 自加1。

三、通过保护变量的安全实现JS私有属性和私有方法(不能被外部访问)

1
2
3
4
5
function Constructor(){
var that = this;
var membername = value;
function membername(...) {...}
}

关于闭包还可以参考:对 js 中 this、apply、call 和闭包的理解

参考

  1. JavaScript 闭包深入理解(closure)
  2. 理解 Javascript 的闭包
  3. 学习Javascript闭包(Closure)
  4. javascript的闭包
坚持原创技术分享,您的支持将鼓励我继续创作!