每写15行代码,就会遇到一个闭包。这句话毫不夸张,因为理论上来说,每一个函数都是一个闭包。
闭包是指那些能够访问独立(自由)变量的函数 (变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以“记忆”它被创建时候的环境。
也正是由于它的特性,还有一种说法,就是:
闭包是代码块和创建该代码块的上下文中数据的结合。
词法作用域
JavaScript 的作用域是词法作用域 (lexical scoping)。我们来解释一下,what’s this,顺便提一下动态作用域。
|
|
这就是词法作用域——在 JavaScript 中,变量的作用域是由它在源代码中所处位置决定的,并且嵌套的函数可以访问到其外层作用域中声明的变量。
词法作用域是在定义时确定的,而动态作用域是在运行时确定的。上例代码在词法作用域中输出的应该是 2 。
什么是闭包
|
|
我要是拿这个例子出来说,肯定要有人不同意的。‘这不是上一篇里面说的作用域链的事情吗,关闭包什么事?’
得了您呢,咱们换个例子瞄两眼。
|
|
众所周知,外部是无法访问内部作用域的内容的。那 bar
缘何可以输出 a
变量。是的,闭包!!!
我们来看一下第二段代码,其实 bar
被声明赋值的时候,就已经绑定了上下文环境了(函数内部所引用到的变量)。
|
|
闭包已经形成了,我们下一次执行的时候,自由变量就可以直接从闭包里寻找了。
第一段代码中的 foo
其实也已经产生闭包了,它绑定的是全局上下文中的自由变量。只不过没有通过 return
来进行一个返回,无法显示出闭包的特性。
共享作用域
通过前面几章的学习,我们应该清楚,如果两个函数是在同一个执行上下文中,实则是共享的同一个外部作用域,两个函数里的自由变量指向的是同一个 [[scopes]]
。
|
|
可以看到 bar.increase
和 bar.decrease
指向的是同一个 [[scopes]]
中的 a
。
那我现在换一换。
|
|
这个时候我门可以发现, bar1
的自由变量在第一次执行的 foo
产生的上下文中, bar2
的在第二次产生的上下文中。两者的 [[scopes]]
已经是不一样的了,所以所引用的自由变量 a
其实是不同的作用域中的,所以也就无法相互影响了。
循环中的错误
这里就用 MDN 里面的例子吧。
这里之所以显示错误,原因在于三个事件响应函数(闭包)共享作用域了,这三者的 [[scopes]]
指向的是同一个 父变量对象的层级链 。 onfocus 的回调被执行时,循环已经完成,且此时 item 变量(由所有三个闭包所共享)已经指向了 helpText 列表中的最后一项。
解决办法就是让三个闭包有各自的执行上下文。
函数重载
JavaScript 中没有函数重载,但是我门可以利用参数检测来实现伪函数重载。
|
|
每一次执行 addMethod
添加方法的时候,都会产生新的执行上下文,同样的 fn 参数以及 old ,在每个新的执行上下文都有不同的值。这是重载能够实现的根本所在。每一个 old
的 [[scopes]]
都指向的是上一次执行 addMethod
产生的 父变量对象的层级链 。
而 this
和 arguments
则是当前函数执行时所指向的当前值。
模块
我们经常使用 IIFE 来进行模块化的应用,它可以创建一个独立的作用域,也只能创建一次,我们可以在其中定义方法,除了这些方法外,其它地方都无法访问,它就是这么地简单,安全。
来看一下类库的封装吧,有两种方式。
|
|
|
|
他们都是通过创建独立作用域,最后会将存储了许多方法的对象暴露出去。避免了全局变量的污染,而且还提高了安全性(变量冲突)。
我们再来看一下现在的模块依赖加载器的核心概念:
|
|
Manager
函数只会执行一次,我们后面通过 MyModules.define
来实现模块化定义的时候,共享的同一个词汇环境,各个模块都会存储在 modules
中。而每次依赖模块执行时,都会从 modules
中取到模块整体并传递给要执行的函数。
很多时候我们在实现类库的时候会在 IIFE 前面添加上 ;
,这是由于 JavaScript 的 ASI 带来的,这也是为了防止有人不在 IIFE 后面添加 ;
从而导致代码压缩的时候出错。
替“内存泄漏”说句话
很多文章,很多书里,都会讲闭包会带来 内存泄漏 ,其实正相反,闭包是来消除内存泄漏的,如果产生了,对不起,那是使用者应该背的锅。正常来说,内存泄漏 是指 IE6 带来的 bug ,现代浏览器的引用计数来进行垃圾处理,几乎不会产生 内存泄漏 。
关于 内存泄漏 ,可以看我的 一种有趣的JavaScript内存泄漏[译]。
闭包带来的妙用,远不止文中所谈的这些,这些都是用来讲解闭包的基本知识并帮助深入一点理解的。打好基础,妙用无穷,后面的学习会水到渠成。一味追求框架,不打理基础,则如高屋建瓴,慎之。