# 第 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() 在之后任何时间进行引用。

闭包的技术要点

  1. 通过函数创建内部作用域
  2. 返回该函数的引用,使其可达,不会被垃圾回收

# 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)包装函数的返回 值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭 包。