如果这里有个需求,即写一个计数器的函数,每调用一次计数器返回值加一:
如何实现,我们可以先简单的写一下:
1 | var index = 1; |
这样写的话,确实每次返回一个递增的数。但是它有以下三个问题:
- 1.这个 index 放在全局,其他代码可能会对他进行修改
- 2.如果我需要同时用两个计数器,但这种写法只能满足一个使用,另一个还想用的话就要再写个 counter2 函数,再定义一个 index2 的全局变量。
- 3.计数器是一个功能,我只希望我的代码里有个 counter 函数就好,其他的最好不要出现。这是稍微有点代码洁癖的都会觉得不爽的。
接下来我们用闭包的方式做一下这个需求:
1 | function counterCreator() { |
这里的 counterCreator 函数只是把上面的几行代码包起来,然后返回了里面的 counter 函数而已。却能同时解决这么多问题,这就是闭包的魅力!
铺垫知识
执行上下文
简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。
函数每次执行,都会创建一个称为执行上下文的内部对象(AO 对象,可理解为函数作用域),这个 AO 对象会保存这个函数中所有的变量值和该函数内部定义的函数的引用。函数每次执行时对应的执行上下文都是独一无二的,正常情况下函数执行完毕执行上下文就会被销毁。
作用域链
在函数定义的时候,他还获得 [[scope]]。这个是里面包含该函数的作用域链,初始值为引用着上一层作用域链里面所有的作用域,后面执行的时候还会将 AO 对象添加进去 。作用域链就是执行上下文对象的集合,这个集合是链条状的。
在函数定义的时候,他还获得 [[scope]]。这个是里面包含该函数的作用域链,初始值为引用着上一层作用域链里面所有的作用域,后面执行的时候还会将 AO 对象添加进去 。作用域链就是执行上下文对象的集合,这个集合是链条状的。
1 | function a () { |
正常情况函数每次执行后 AO 对象都被销毁,且每次执行时都是生成新的 AO 对象。我们得出这个结论: 只要是这个函数每次调用的结果不一样,那么这个函数内部一定是使用了函数外部的变量。
垃圾回收
如何确定哪些内存需要回收,哪些内存不需要回收,这依赖于活对象这个概念。我们可以这样假定:一个对象为活对象当且仅当它被一个根对象 或另一个活对象指向。根对象永远是活对象。
1 | function a () { |
分析闭包结构
1 | // 生成闭包的函数 |
闭包的创造函数必定包含两部分:
- 1.一些闭包函数执行时依赖的变量,每次执行闭包函数时都能访问和修改
- 2.返回的函数,这个函数中必定使用到上面所说的那些变量而上面这两句代码很重要,它其实是把闭包函数赋值给了一个变量,这个变量是一个活对象,这活对象引用了闭包函数,闭包函数又引用了 AO 对象,所以这个时候 AO 对象也是一个活对象。此时闭包函数的作用域链得以保存,不会被垃圾回收机制所回收。
1
2
3// 被赋值的闭包函数
var counterA = counterCreator();
var counterB = counterCreator();
当我们想重新创建一个新的计数器时,只需要重新再调用一次 counterCreator, 他会新生成了一个新的执行期上下文,所以 counterB 与 counterA 是互不干扰的。
counterCreator执行:
counterCreator 执行完毕,返回 counter:
总结
闭包的原理,就是把闭包函数的作用域链保存了下来。
使用闭包
一个简单的防抖函数:
第一步,先把闭包的架子搭起来,因为我们已经分析了闭包生成函数内部一定有的两部分内容。
1 | function debunce(func, timeout) { |
第二步: 把闭包第一次执行的情况写出来
1 | function debunce(func, timeout) { |
第三步: 加上一些判断条件。就像我们最开始写计数器的 index 一样,不过这一次你不是把变量写在全局下,而是写在闭包生成器的内部。
1 | function debunce(func, timeout) { |
闭包的应运场景
模块化
例 NodeJS 模块化原理:
NodeJS 会给每个文件包上这样一层函数,引入模块使用 require,导出使用 exports,而那些文件中定义的变量也将留在这个闭包中,不会污染到其他地方。
1 | (funciton(exports, require, module, __filename, __dirname) { |
高阶函数
一些使用闭包的经典例子:
- 节流函数
- 柯里化(Currying)
- 组合(Composing)
- bind 的实现