
4.5 闭包
在讲闭包之前,先了解一下什么是自由变量。
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
示例代码:

上述代码中,对于函数foo来说,a既不是函数参数也不是函数的局部变量的变量,因此a属于自由变量。
4.5.1 什么是闭包
在ECMAScript中,闭包(Closure)是指能够访问自由变量的函数。
按照以上的概念,我们可以说所有的函数都是闭包,因为它们都在创建的时候就保存了上层上下文的作用域链,观察如下代码。

ECMAScript使用的是词法作用域(Lexical scoping,又称“静态作用域”),即在函数创建时,就保存上层上下文的作用域链。上述代码中,在foo函数创建时,其所使用的变量a是已经在上下文中静态保存好的,因此,在执行foo()时a的值为1。
而任何函数,在其创建时保存的上层上下文的作用域中都有全局的自由变量global(在浏览器中,global为window),因此说,所有函数都是闭包。
4.5.2 实践中的闭包
上面说的是理论上的闭包,但在实践中,闭包不仅只是能够访问自由变量的函数,闭包还是指引用了自由变量的,并且被引用的自由变量将和这个函数一同存在的函数,在创建该函数的上下文已经销毁时,该函数仍然存在。
示例代码:

上述代码中,foo函数执行后返回了一个匿名函数,该函数引用了自由变量a,而在foo()执行完毕后,创建该函数的环境已经销毁,但该函数并没有被销毁,因此foo()的返回值就是一个闭包。
闭包会使引用的自由变量不能被清除,这就使闭包比其他函数占用的内存更多,但这也是闭包的强大之处,以下是一个使用闭包的示例。

再来看一个面试中经常遇到的题目。

这3个函数创建时均使用的是已经在上下文中静态保存好的变量i,而在for循环结束时,变量i的值为3,当data0执行时,其所引用的自由变量i的值为3,因此输出3。
我们的目标是输出0、1、2,上面的示例显然无法实现这个需求,利用闭包可以很轻松地解决这个问题,示例如下。

练习
- 尝试使用闭包。