闭包可能有不同的名字,比较常见的有:
- 闭包;
- 作用域链;
- 执行上下文;
- this 值;
实际上,尽管它们表示不同的意思的术语,但所指向的几乎是一部分知识,那就是函数执行过程相关的知识。
我们可以简单理解下,闭包其实就是一个绑定了执行环境的函数。闭包与普通函数的区别是,它携带了执行的环境,就像人在外星中需要自带吸氧的装备一样,这个函数也带有在程序中生存的环境。
var 声明与赋值
我们来分析一段代码:
var b = 1;
通常我们认为它声明了b,并且为它赋值为1,var 声明作用域函数执行的作用域。也就是说,var 会穿透for、if 等语句。
在只有 var,没有 let 的时代,诞生了一个技巧,叫做立即执行的函数表达式(IIFE),通过创建一个函数,并且立即执行,来构造一个新的域,从而控制 var 的范围。
由于语法规定了 function 关键字开头是函数声明,所以要想让函数变成函数表达式,我们必须加点东西,最常见的做法是加括号;
(function(){
var a;
...
})();
(function(){
var a;
...
})();
但是,括号有个缺点,那就是如果上一行代码不写分号,括号会被解释为上一行代码最末的函数调用,产生不符合预期的结果,并且难以调试的行为,加号等运算符也有类似的问题。所以一些推荐不加分号的代码风格规范,会要求在括号前面加上分号。
;(function(){
var a;
...
})()
;(function(){
var a;
...
})()
比较推荐的写法是使用 void 关键字:
void function(){
var a;
...
}();
值得特别注意的是,有时候 var 的特性会导致声明的变量和被赋值的变量是两个 b,Javascript 中有特例,那就是使用 with 的时候:
var b;
void function(){
var env = { b: 1 };
b = 2;
console.log("In function b:", b);
with(env) {
var b = 3;
console.log("In with b:", b);
}
}();
console.log("Global b:", b);
let
let 是 ES6 开始引入的新的变量声明方式,比起 var 的诸多弊病,let 做了非常明确的梳理和规定。
为了实现 let ,Javascript 在运行时引入了块级作用域。也就是说,在 let 出现之前,Javascript 的 if for 等语句皆不产生作用域。
以下语句会产生 let 使用的作用域:
- for;
- if;
- switch;
- try/catch/finally