闭包与普通函数的区别是,它携带了执行的环境,就像人在外星中需要自带吸氧的装备一样,这个函数也带有在程序中生存的环境。
1.闭包的组成部分
环境部分
环境:函数的词法环境(执行上下文的一部分)
标识符列表:函数中用到的未声明的变量
表达式部分:函数体
至此,我们可以认为,JavaScript 中的函数完全符合闭包的定义。它的环境部分是函数词法环境部分组成,它的标识符列表是函数中用到的未声明变量,它的表达式部分就是函数体。
2.执行上下文
在 ES5 中,我们改进了命名方式,把执行上下文最初的三个部分改为下面这个样子。
lexical environment:词法环境,当获取变量时使用。
variable environment:变量环境,当声明变量时使用。
this value:this 值。
比如,我们看以下的这段 JavaScript 代码:
var b = {}
let c = 1
this.a = 2;
要想正确执行它,我们需要知道以下信息:
- var 把 b 声明到哪里;
- b 表示哪个变量;
- .b 的原型是哪个对象;
- let 把 c 声明到哪里;
- this 指向哪个对象。
这些信息就需要执行上下文来给出了,这段代码出现在不同的位置,甚至在每次执行中,会关联到不同的执行上下文,所以,同样的代码会产生不一样的行为。
var 声明与赋值
我们来分析一段代码:
var b = 1
通常我们认为它声明了 b,并且为它赋值为 1,var 声明作用域函数执行的作用域。也就是说,var 会穿透 for 、if 等语句。
在只有 var,没有 let 的旧 JavaScript 时代,诞生了一个技巧,叫做:立即执行的函数表达式(IIFE),通过创建一个函数,并且
立即执行,来构造一个新的域,从而控制 var 的范围。
由于语法规定了 function 关键字开头是函数声明,所以要想让函数变成函数表达式,我们必须得加点东西,最常见的做法是加括
号。
(function(){
var a;
//code
}());
(function(){
var a;
//code
})();
但是,括号有个缺点,那就是如果上一行代码不写分号,括号会被解释为上一行代码最末的函数调用,产生完全不符合预期,并且难以调试的行为, 加号等运算符也有类似的问题。所以一些推荐不加分号的代码风格规范,会要求在括号前面加上分号。
;(function(){
var a;
//code
}())
;(function(){
var a;
//code
})()
我比较推荐的写法是使用 void 关键字。也就是下面的这种形式。
void function(){
var a;
//code
}();
let
为了实现 let,JavaScript 在运行时引入了块级作用域。也就是说,在 let 出现之前,JavaScript 的 if for 等语句皆不产生作用域。
我简单统计了下,以下语句会产生 let 使用的作用域:
- for;
- if;
- switch;
- try/catch/finally。