塊級作用域
ES5 只有全局作用域和函數(shù)作用域,沒有塊級作用域,這帶來很多不合理的場景。
1. 內(nèi)層變量可能會覆蓋外層變量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
上面代碼的原意是,if代碼塊的外部使用外層的tmp變量,內(nèi)部使用內(nèi)層的tmp變量。但是,函數(shù)f執(zhí)行后,輸出結(jié)果為undefined,原因在于變量提升,導(dǎo)致內(nèi)層的tmp變量覆蓋了外層的tmp變量
2. 用來計數(shù)的循環(huán)變量泄露為全局變量
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面代碼中,變量i只用來控制循環(huán),但是循環(huán)結(jié)束后,它并沒有消失,泄露成了全局變量。
ES6 的塊級作用域
let實際上為 JavaScript 新增了塊級作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函數(shù)有兩個代碼塊,都聲明了變量n,運行后輸出 5。這表示外層代碼塊不受內(nèi)層代碼塊的影響。如果兩次都使用var定義變量n,最后輸出的值才是 10。
ES6 允許塊級作用域的任意嵌套
{{{{{let insane = 'Hello World'}}}}};
上面代碼使用了一個五層的塊級作用域。外層作用域無法讀取內(nèi)層作用域的變量。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 報錯
}}}};
內(nèi)層作用域可以定義外層作用域的同名變量。
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
塊級作用域與函數(shù)聲明
函數(shù)能不能在塊級作用域之中聲明?這是一個相當令人混淆的問題。
ES5 規(guī)定,函數(shù)只能在頂層作用域和函數(shù)作用域之中聲明,不能在塊級作用域聲明。
// 情況一
if (true) {
function f() {}
}
// 情況二
try {
function f() {}
} catch(e) {
// ...
}
上面兩種函數(shù)聲明,根據(jù) ES5 的規(guī)定都是非法的。
但是,瀏覽器沒有遵守這個規(guī)定,為了兼容以前的舊代碼,還是支持在塊級作用域之中聲明函數(shù),因此上面兩種情況實際都能運行,不會報錯。
ES6 引入了塊級作用域,明確允許在塊級作用域之中聲明函數(shù)。ES6 規(guī)定,塊級作用域之中,函數(shù)聲明語句的行為類似于let,在塊級作用域之外不可引用。
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重復(fù)聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
}
f();
}());
上面代碼在 ES5 中運行,會得到“I am inside!”,因為在if內(nèi)聲明的函數(shù)f會被提升到函數(shù)頭部,實際運行的代碼如下。
// ES5 環(huán)境
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}());
ES6 就完全不一樣了,理論上會得到“I am outside!”。因為塊級作用域內(nèi)聲明的函數(shù)類似于let,對作用域之外沒有影響。但是,如果你真的在 ES6 瀏覽器中運行一下上面的代碼,是會報錯的,這是為什么呢?
原來,如果改變了塊級作用域內(nèi)聲明的函數(shù)的處理規(guī)則,顯然會對老代碼產(chǎn)生很大影響。為了減輕因此產(chǎn)生的不兼容問題,ES6 在附錄 B里面規(guī)定,瀏覽器的實現(xiàn)可以不遵守上面的規(guī)定,有自己的行為方式。
- 允許在塊級作用域內(nèi)聲明函數(shù)。
- 函數(shù)聲明類似于
var,即會提升到全局作用域或函數(shù)作用域的頭部。 - 同時,函數(shù)聲明還會提升到所在的塊級作用域的頭部。
注意,上面三條規(guī)則只對 ES6 的瀏覽器實現(xiàn)有效,其他環(huán)境的實現(xiàn)不用遵守,還是將塊級作用域的函數(shù)聲明當作let處理。
根據(jù)這三條規(guī)則,在瀏覽器的 ES6 環(huán)境中,塊級作用域內(nèi)聲明的函數(shù),行為類似于var聲明的變量。
// 瀏覽器的 ES6 環(huán)境
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重復(fù)聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
}
f();
}());
上面的代碼在符合 ES6 的瀏覽器中,都會報錯,因為實際運行的是下面的代碼
// 瀏覽器的 ES6 環(huán)境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
考慮到環(huán)境導(dǎo)致的行為差異太大,應(yīng)該避免在塊級作用域內(nèi)聲明函數(shù)。如果確實需要,也應(yīng)該寫成函數(shù)表達式,而不是函數(shù)聲明語句。
// 函數(shù)聲明語句
{
let a = 'secret';
function f() {
return a;
}
}
// 函數(shù)表達式
{
let a = 'secret';
let f = function () {
return a;
};
}
另外,還有一個需要注意的地方。ES6 的塊級作用域允許聲明函數(shù)的規(guī)則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯。
// 不報錯
'use strict';
if (true) {
function f() {}
}
// 報錯
'use strict';
if (true)
function f() {}
do 表達式
本質(zhì)上,塊級作用域是一個語句,將多個操作封裝在一起,沒有返回值。
{
let t = f();
t = t * t + 1;
}
上面代碼中,塊級作用域?qū)蓚€語句封裝在一起。但是,在塊級作用域以外,沒有辦法得到t的值,因為塊級作用域不返回值,除非t是全局變量
現(xiàn)在有一個提案,使得塊級作用域可以變?yōu)楸磉_式,也就是說可以返回值,辦法就是在塊級作用域之前加上do,使它變?yōu)?code>do表達式,然后就會返回內(nèi)部最后執(zhí)行的表達式的值。
let x = do {
let t = f();
t * t + 1;
};
上面代碼中,變量x會得到整個塊級作用域的返回值(t * t + 1)。
-_-! 這個提案不止有沒有通過
轉(zhuǎn)自 阮一峰 ECMAScript 6 入門