《ECMAScript 6入門》筆記2
阿新 • • 發佈:2019-02-20
塊級作用域
ES5只有全域性作用域和函式作用域,沒有塊級作用域,這帶來很多不合理的場景。
第一種場景,內層變數可能會覆蓋外層變數。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
上面程式碼的原意是,if程式碼塊的外部使用外層的tmp變數,內部使用內層的tmp變數。但是,函式f執行後,輸出結果為undefined,原因在於變數提升,導致內層的tmp變數覆蓋了外層的tmp變數。
第二種場景,用來計數的迴圈變數洩露為全域性變數。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面程式碼中,變數i只用來控制迴圈,但是迴圈結束後,它並沒有消失,洩露成了全域性變數。
ES6的塊級作用域
let實際上為JavaScript新增了塊級作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函式有兩個程式碼塊,都聲明瞭變數n,執行後輸出5。這表示外層程式碼塊不受內層程式碼塊的影響。如果兩次都使用var定義變數n,最後輸出的值才是10。
ES6允許塊級作用域的任意巢狀。
{{{{{let insane = 'Hello World'}}}}};
上面程式碼使用了一個五層的塊級作用域。外層作用域無法讀取內層作用域的變數。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 報錯
}}}};
內層作用域可以定義外層作用域的同名變數。
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
塊級作用域的出現,實際上使得獲得廣泛應用的立即執行函式表示式(IIFE)不再必要了。
// IIFE 寫法 (function () { var tmp = ...; ... }()); // 塊級作用域寫法 { let tmp = ...; ... }
塊級作用域與函式宣告
ES5規定,函式只能在頂層作用域和函式作用域之中宣告,不能在塊級作用域宣告。
// 情況一
if (true) {
function f() {}
}
// 情況二
try {
function f() {}
} catch(e) {
// ...
}
上面兩種函式宣告,根據ES5的規定都是非法的。
但是,瀏覽器沒有遵守這個規定,為了相容以前的舊程式碼,還是支援在塊級作用域之中宣告函式,因此上面兩種情況實際都能執行,不會報錯。
ES6引入了塊級作用域,明確允許在塊級作用域中宣告函式。ES6規定,塊級作用域之中,函式宣告語句的行為類似於let,在塊級作用域之外不可引用。
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重複宣告一次函式f
function f() { console.log('I am inside!'); }
}
f();
}());
上面程式碼在ES5中執行,會得到“I am inside!”,因為在if內宣告的函式f會被提升到函式頭部,實際執行的程式碼如下。
// ES5 環境
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}());
ES6就完全不一樣了,理論上會得到“I am outside!”。因為塊級作用域內宣告的函式類似於let,對作用域之外沒有影響。但是,如果你真的在ES6瀏覽器中執行一下上面的程式碼,是會報錯的,這是為什麼呢?
原來,如果改變了塊級作用域內宣告的函式的處理規則,顯然會對老程式碼產生很大的影響。為了減輕因此產生的不相容問題,ES6在附錄B裡面規定,瀏覽器的實現可以不遵守上面的規定,有自己的行為方式。
-允許在塊級作用域內宣告函式。
-函式宣告類似於var,即會提升到全域性作用域或函式作用域的頭部。
-同時,函式宣告還會提升到所在的塊級作用域的頭部。
注意,上面三條規則只對ES6的瀏覽器實現有效,其他環境的實現不用遵守,還是將塊級作用域的函式聲明當作let處理。
根據這三條規則,在瀏覽器的ES6環境中,塊級作用域內宣告的函式,行為類似於var宣告的變數。
// 瀏覽器的 ES6 環境
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重複宣告一次函式f
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
上面的程式碼在符合ES6的瀏覽器中,都會報錯,因為實際執行的是下面的程式碼。
// 瀏覽器的 ES6 環境
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
考慮到環境導致的行為差異太大,應該避免在塊級作用域內宣告函式。如果確實需要,也應該寫成函式表示式,而不是函式宣告語句。
// 函式宣告語句
{
let a = 'secret';
function f() {
return a;
}
}
// 函式表示式
{
let a = 'secret';
let f = function () {
return a;
};
}
ES6的塊級作用域允許宣告函式的規則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯。
// 不報錯
'use strict';
if (true) {
function f() {}
}
// 報錯
'use strict';
if (true)
function f() {}
do表示式
本質上,塊級作用域是一個語句,將多個操作封裝在一起,沒有返回值。
{
let t = f();
t = t * t + 1;
}
上面程式碼中,塊級作用域將兩個語句封裝在一起。但是,在塊級作用域以外,沒有辦法得到t的值,因為塊級作用域不返回值,除非t是全域性變數。
現在有一個提案,使得塊級作用域可以變為表示式,也就是說可以返回值,辦法就是在塊級作用域之前加上do,使它變為do表示式。
let x = do {
let t = f();
t * t + 1;
};
上面程式碼中,變數x會得到整個塊級作用域的返回值。