闭包可能有不同的名字,比较常见的有:

  • 闭包;
  • 作用域链;
  • 执行上下文;
  • this 值;

实际上,尽管它们表示不同的意思的术语,但所指向的几乎是一部分知识,那就是函数执行过程相关的知识。

我们可以简单理解下,闭包其实就是一个绑定了执行环境的函数。闭包与普通函数的区别是,它携带了执行的环境,就像人在外星中需要自带吸氧的装备一样,这个函数也带有在程序中生存的环境。

var 声明与赋值

我们来分析一段代码:

  1. var b = 1;

通常我们认为它声明了b,并且为它赋值为1,var 声明作用域函数执行的作用域。也就是说,var 会穿透for、if 等语句。

在只有 var,没有 let 的时代,诞生了一个技巧,叫做立即执行的函数表达式(IIFE),通过创建一个函数,并且立即执行,来构造一个新的域,从而控制 var 的范围。

由于语法规定了 function 关键字开头是函数声明,所以要想让函数变成函数表达式,我们必须加点东西,最常见的做法是加括号;

  1. (function(){
  2. var a;
  3. ...
  4. })();
  5. (function(){
  6. var a;
  7. ...
  8. })();

但是,括号有个缺点,那就是如果上一行代码不写分号,括号会被解释为上一行代码最末的函数调用,产生不符合预期的结果,并且难以调试的行为,加号等运算符也有类似的问题。所以一些推荐不加分号的代码风格规范,会要求在括号前面加上分号。

  1. ;(function(){
  2. var a;
  3. ...
  4. })()
  5. ;(function(){
  6. var a;
  7. ...
  8. })()

比较推荐的写法是使用 void 关键字:

  1. void function(){
  2. var a;
  3. ...
  4. }();

值得特别注意的是,有时候 var 的特性会导致声明的变量和被赋值的变量是两个 b,Javascript 中有特例,那就是使用 with 的时候:

  1. var b;
  2. void function(){
  3. var env = { b: 1 };
  4. b = 2;
  5. console.log("In function b:", b);
  6. with(env) {
  7. var b = 3;
  8. console.log("In with b:", b);
  9. }
  10. }();
  11. console.log("Global b:", b);

let

let 是 ES6 开始引入的新的变量声明方式,比起 var 的诸多弊病,let 做了非常明确的梳理和规定。

为了实现 let ,Javascript 在运行时引入了块级作用域。也就是说,在 let 出现之前,Javascript 的 if for 等语句皆不产生作用域。

以下语句会产生 let 使用的作用域:

  • for;
  • if;
  • switch;
  • try/catch/finally