且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《深入理解JavaScript》——1.13 变量作用域和闭包

更新时间:2022-10-13 14:28:40

本节书摘来自异步社区《深入理解JavaScript》一书中的第1章,第1.13节,作者: 【美】AxelRauschmayer(罗彻麦尔)译者: 王玉林 , 杜欢 , 庄婷婷 , 章子鹏,更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.13 变量作用域和闭包

在JavaScript中,通过在变量前使用var语句声明变量:

  《深入理解JavaScript》——1.13 变量作用域和闭包

你可以使用单个var语句声明和初始化多个变量:

  《深入理解JavaScript》——1.13 变量作用域和闭包

但是我推荐使用单独声明每一个变量(原因参考26.4.1“语法”)。因此,我会将之前的语句重写为:

  《深入理解JavaScript》——1.13 变量作用域和闭包

由于前置的缘故(参考1.13.2“变量的提升特性”),通常它的***实践是在一个函数的开始部分声明变量。

1.13.1 变量是函数作用域的
一个变量的作用域总是完整的函数(相对于当前块)。例如:

  《深入理解JavaScript》——1.13 变量作用域和闭包

我们可以看到变量tmp并不局限于(1)行;直到函数结束它都存在。

1.13.2 变量的提升特性
所有变量声明都会被提升:声明会被移动到函数的开始处,而赋值则仍然会在原来的位置进行。例如,以下函数中的变量会被认为是在标记为(1)的这行声明的:

  《深入理解JavaScript》——1.13 变量作用域和闭包

然而在程序内部,上述函数的执行过程其实是这样的:

  《深入理解JavaScript》——1.13 变量作用域和闭包

1.13.3 闭包
每个函数都和它周围的变量保持着连接,哪怕它离开被创建时的作用域也是如此。例如:

  《深入理解JavaScript》——1.13 变量作用域和闭包

函数从标记为(1)的这行开始被创建,在创建结束后即离开它的上下文环境,但它仍然保持着和start的连接:

  《深入理解JavaScript》——1.13 变量作用域和闭包

函数以及它所连接的周围作用域中的变量即为闭包。所以,create Incrementor()的返回其实就是一个闭包。

1.13.4 IIFE模式:引入一个新的作用域
有时你会想要引入一个新的作用域,例如,防止一个变量变成全局变量。在JavaScript中,不能通过块来做,必须使用函数。不过有一种模式可以将函数当做类似块的方式来使用。这种模式被称作为IIFE(立即调用函数表达式,发音为“iffy”):

  《深入理解JavaScript》——1.13 变量作用域和闭包

请务必键入以上示例(注释除外)。IIFE是一个在定义之后就被立即调用的函数表达式。在函数内部,会有一个新的作用域,以防止tmp变成全局变量。更多关于IIFE的细节,参见16.6“通过IIFE引入新的作用域”。

IIFE用例:闭包造成的无意共享
闭包会持续地保持与外部变量的连接,而这有时候并不是你想要的:

 《深入理解JavaScript》——1.13 变量作用域和闭包

标记为(1)的这行返回值总是i的当前值,而并非函数被创建时的值。在循环结束之后,i的值为5,所以数组中所有的函数都返回这个数值。如果你想要标记(1)这行的函数获得当前i值的一个快照,那么你可以使用IIFE:

  《深入理解JavaScript》——1.13 变量作用域和闭包