1.13 变量作用域和闭包
在JavaScript中,通过在变量前使用var语句声明变量:
你可以使用单个var语句声明和初始化多个变量:
但是我推荐使用单独声明每一个变量(原因参考26.4.1“语法”)。因此,我会将之前的语句重写为:
由于前置的缘故(参考1.13.2“变量的提升特性”),通常它的最佳实践是在一个函数的开始部分声明变量。
1.13.1 变量是函数作用域的
一个变量的作用域总是完整的函数(相对于当前块)。例如:
我们可以看到变量tmp并不局限于(1)行;直到函数结束它都存在。
1.13.2 变量的提升特性
所有变量声明都会被提升:声明会被移动到函数的开始处,而赋值则仍然会在原来的位置进行。例如,以下函数中的变量会被认为是在标记为(1)的这行声明的:
然而在程序内部,上述函数的执行过程其实是这样的:
1.13.3 闭包
每个函数都和它周围的变量保持着连接,哪怕它离开被创建时的作用域也是如此。例如:
函数从标记为(1)的这行开始被创建,在创建结束后即离开它的上下文环境,但它仍然保持着和start的连接:
函数以及它所连接的周围作用域中的变量即为闭包。所以,create Incrementor()的返回其实就是一个闭包。
1.13.4 IIFE模式:引入一个新的作用域
有时你会想要引入一个新的作用域,例如,防止一个变量变成全局变量。在JavaScript中,不能通过块来做,必须使用函数。不过有一种模式可以将函数当做类似块的方式来使用。这种模式被称作为IIFE(立即调用函数表达式,发音为“iffy”):
请务必键入以上示例(注释除外)。IIFE是一个在定义之后就被立即调用的函数表达式。在函数内部,会有一个新的作用域,以防止tmp变成全局变量。更多关于IIFE的细节,参见16.6“通过IIFE引入新的作用域”。
IIFE用例:闭包造成的无意共享
闭包会持续地保持与外部变量的连接,而这有时候并不是你想要的:
标记为(1)的这行返回值总是i的当前值,而并非函数被创建时的值。在循环结束之后,i的值为5,所以数组中所有的函数都返回这个数值。如果你想要标记(1)这行的函数获得当前i值的一个快照,那么你可以使用IIFE: