![ES6标准入门(第3版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/811/47378811/b_47378811.jpg)
2.2 块级作用域
2.2.1 为什么需要块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域,这导致很多场景不合理。
第一种场景,内层变量可能会覆盖外层变量。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/50_3.jpg?sign=1739530296-ZKZ4Fs5XUdPYZUfGZlNSn26nDT2njylC-0-5a266fe97be86599b9fa326fdd55fb0c)
以上代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升导致内层的tmp变量覆盖了外层的tmp变量。
第二种场景,用来计数的循环变量泄露为全局变量。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/50_4.jpg?sign=1739530296-Ua54GDCJNVfPeXAacopvje2XgUNAjFjx-0-554650ff5a74bd8e2660793f49171c46)
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/51_1.jpg?sign=1739530296-jmjevylUV2O2Ec179KXu3MCY3rnI7x8Q-0-91c4b82c80065f4d7aa11cab0d8fda6e)
上面的代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,而是泄露成了全局变量。
2.2.2 ES6的块级作用域
let实际上为JavaScript新增了块级作用域。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/51_2.jpg?sign=1739530296-RsG6s4u1d8hXUjsePvA8wfsqoPKY0uL8-0-f9b5e39588f9361279f597b1c328fec0)
上面的函数有两个代码块,都声明了变量 n,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10。
ES6允许块级作用域的任意嵌套。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/51_3.jpg?sign=1739530296-upMm6sxfqsZMFvYkld688sSRiJpElVJN-0-0ba6eb83f0f83f9169a7033fa60ab4fd)
上面的代码使用了一个5层的块级作用域。外层作用域无法读取内层作用域的变量。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/51_4.jpg?sign=1739530296-70FAN2V1GMa49cmKYzjVW87fnjzMx6nP-0-d71bcea1263ae5e8f377674066efa884)
内层作用域可以定义外层作用域的同名变量。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/51_5.jpg?sign=1739530296-2IHYvpo201ppYPQpBKU4msBx18c9D9HH-0-f8ad76f42f0b5b1aa68b80603bc598a1)
块级作用域的出现,实际上使得获得广泛应用的立即执行匿名函数(IIFE)不再必要了。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/52_1.jpg?sign=1739530296-tiTJuzvq7MJ4S4Cl4bXFPxIODeT6VEcp-0-c5dbd5a165c9129fe8131450b5e5a838)
2.2.3 块级作用域与函数声明
函数能不能在块级作用域之中声明?这是一个相当令人困惑的问题。
ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/52_2.jpg?sign=1739530296-VyKEdAWrFrNPVaZCZRK9i3Sjeynp3mJE-0-98a517c409bfdb3566f3ba6364617f5e)
上面两种函数声明在ES5中都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际上都能运行,并不会报错。
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,在块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/52_3.jpg?sign=1739530296-hW326gRUVNN32fnQhrLEXMG8on33VM8M-0-3cbc4f727a48f46b8094f4eb751190ed)
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/53_1.jpg?sign=1739530296-1tbv4Snup2DjiOMNwNsYKXiWXtuS3a24-0-3c50b920ee63e1ca14d1d854e451ed7e)
以上代码在ES5中运行会得到 I am inside!,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/53_2.jpg?sign=1739530296-KetGYupHg92bbDhZOFEuHunRq54f6tYV-0-593056449e98ebf1c1e38ea8d6ebfe58)
而在ES6中运行就完全不一样了,理论上会得到 I am outside!。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。但是,如果真的在ES6浏览器中运行上面的代码,是会报错的,这是为什么呢?
原来,如果改变了块级作用域内声明的函数的处理规则,显然会对旧代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在附录 B(www.ecma-international.org/ecma-262/6.0/index.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics)中规定,浏览器的实现可以不遵守上面的规定,而有自己的行为方式(stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6),具体如下。
· 允许在块级作用域内声明函数。
· 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
· 同时,函数声明还会提升到所在的块级作用域的头部。
注意!
上面3条规则只对ES6的浏览器实现有效,其他环境的实现不用遵守,仍旧将块级作用域的函数声明当作let处理即可。
根据这3条规则,在浏览器的ES6环境中,块级作用域内声明函数的行为类似于var声明变量。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/54_1.jpg?sign=1739530296-q49f98vvKkbyO4rY5JJxRd4OJoLMR8K5-0-f225e4fc3fc6dca1d776e63c43542b8b)
上面的代码在符合ES6的浏览器中都会报错,因为实际运行的是以下代码。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/54_2.jpg?sign=1739530296-MPOUUuR9QX7AMZ7pyPShVXl6ycEC7XcF-0-50f6be459349fa2248167df998cb5e2b)
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式的形式,而不是函数声明语句。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/54_3.jpg?sign=1739530296-8CtCzTTDGYtWnjHUTXqxClaIQFeFxARV-0-b0e4bd5b348cfbf1b1f9011d4be276a4)
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/55_1.jpg?sign=1739530296-Daq9LOCJAqmZrExv9ZUqBpWAAKmNrqvT-0-bf161feb58d51e8e35f1b21e3e8fb996)
另外,还有一个需要注意的地方。ES6 的块级作用域允许声明函数的规则只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/55_2.jpg?sign=1739530296-RmJg6Y97yczHe6jWwPVT8zOyiAFs4ZJf-0-ca8620c26da93c0cc4cf5fab4dd54043)
2.2.4 do表达式
本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/55_3.jpg?sign=1739530296-enDsjn2xVJA04MtyXZD6BThQ3NRaCRcX-0-b0b1a2ade67b6780e4a78fd4bf4f690e)
上面的代码中,块级作用域将两个语句封装在一起。但是,在块级作用域以外,没有办法得到t的值,因为块级作用域不返回值,除非t是全局变量。
现在有一个提案(wiki.ecmascript.org/doku.php?id=strawman:do expressions),使得块级作用域可以变为表达式,即可以返回值,办法就是在块级作用域之前加上do,使它变为do表达式。
![](https://epubservercos.yuewen.com/F720A1/26763854109471806/epubprivate/OEBPS/Images/55_4.jpg?sign=1739530296-kGkAD1mNGrd76XjA2smfrypYjlgC8rof-0-5acdc4c52927f1931d8e9393fa3f2f36)
上面的代码中,变量x会得到整个块级作用域的返回值。