# 第 5 章 作用域闭包
在 JavaScript 中闭包无处不在,你只需要能够识别并拥抱它。
# 5.1 启示
闭包是基于词法作用域书写代码时所产生的自然结果,你甚至不需要为了利用它们而有意识地创建闭包。闭包的创建和使用在你的代码中随处可见。你缺少的是根据你自己的意愿来识别、拥抱和影响闭包的思维环境
# 5.2 实质
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的效果
bar() 显然可以被正常执行。但是在这个例子中,它在自己定义的词法作用域以外的地方执行。 在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。
而闭包的“神奇”之处正是可以阻止这件事情的发生。拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用。
闭包的技术要点
- 通过函数创建内部作用域
- 返回该函数的引用,使其可达,不会被垃圾回收
# 5.4 循环和闭包
要说明闭包,for 循环是最常见的例子。
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
这段代码在运行时会以每秒一次的频率输出五次 6。
使用 IIFE 并创建内部变量
for (var i = 1; i <= 5; i++) {
(function (j) {
// 新的函数作用域
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i); // 内部变量
}
使用 let 创建循环变量,每次迭代都会声明
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
# 5.5 模块
# 5.6 小结
闭包就好像从 JavaScript 中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人才能够到达那里。但实际上它只是一个标准,显然就是关于如何在函数作为值按需传递的词法环境中书写代码的。
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。
如果没能认出闭包,也不了解它的工作原理,在使用它的过程中就很容易犯错,比如在循环中。但同时闭包也是一个非常强大的工具,可以用多种形式来实现模块等模式
模块有两个主要特征: (1)为创建内部作用域而调用了一个包装函数; (2)包装函数的返回 值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭 包。