什么是“闭包”?
通俗讲闭包就是–定义在函数内部的函数可以访问外层作用域的变量。当然这只是闭包的表象,要理解闭包我们先要澄清两个容易混淆的概念。
执行环境(Execute Context)
当进入(开始执行)一段可执行代码时,便生成一个执行环境对象。
一个执行环境(Execute Context)包含三个要素:
- 词法环境 – LexicalEnvironment
- 变量环境 – VariableEnvironment
- This绑定 - ThisBinding
注:词法环境在我理解就是所谓的“作用域”。
作用域(Scope)
JavaScript只有两种作用域(没有块级作用域)
全局作用域:即全局可以访问的作用域,包括下面一些
- 最外层定义的变量和函数
- 未定义直接赋值的变量
- Global对象的属性
局部作用域:只有固定代码片段可以访问,创建局部作用域(词法环境)的方式有
- 执行函数
- 执行Catch语句
- with
作用域链(Scope Chain)
在JavaScript中,函数有一个内部属性[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
1 2 3 4 5 6 |
|
例如上面函数foo是在全局作用域上创建的,所以全局对象就填充到其作用域链上了。在执行foo函数的时候,会创建一个新的执行环境(Execute Context)包括一个新的词法环境(作用域),并将其推到作用域链的前端。
现在回过来想想为什么JavaScript语言会有闭包就很容易理解了。因为ECMAScript中变量解析是一个自内向外查找过程,而非绑定过程。
闭包的实践
循环异步调用的问题
闭包的一个典型的应用就是循环绑定事件。
1 2 3 4 5 |
|
上面的代码存在著名的”last value”问题,最终结果是点击任何一个box都提示点击的是9号。解决的方案有很多种,其中一种就是利用闭包
1 2 3 4 5 6 7 |
|
直接执行一个匿名函数,并将索引值i
作为参数传入,在匿名函数内部返回另外一个匿名函数作为事件绑定的回调函数。根据闭包的原理,内部的匿名函数可以访问外层匿名函数的局部变量index,以此实现点击每个box可以弹出不同的提示。
私有变量和方法
上一篇文章提到过用闭包模拟OOP中的私有变量和方法,在此不在赘述了。
函数的绑定
由于JavaScript的异步特性,我们经常需要在执行一个操作时传入一个回调函数,默认情况下这个函数会绑定到global对象上执行,大多数时候这显然不是我们所要的,我们希望能够指定回调函数的this对象。在ES 5里面函数有一个bind方法,可以原生支持函数this的绑定,而在ES 3里我们也可以用闭包来模拟这个方法的实现
1 2 3 4 5 6 7 8 |
|
<未完待续>