本文知識點主要整理自《深刻理解 ES6(Understanding ECMAScript 6)》中文版實體書的內容,部分地方會加上本身的理解,同時書中敘述比較模糊的部分參考了阮一峯老師的《ECMAScript 6 入門》與網絡上其餘大佬們的博客、問答,篇幅有限沒法一一列出,在此表示感謝。javascript
在函數做用域或者全局做用域中使用 var
聲明的變量,不管是在哪裏進行聲明,都會被當成在當前做用域的頂部進行的變量聲明,這就是變量提高(Hoisting)。java
如:git
在函數 temp
中聲明局部變量,與在函數 temp
後聲明全局變量es6
function temp (condition) {
if (condition) {
var a = 1; // 函數的局部變量
} else {
console.log(a); // undefined
}
}
var b = 2; // 所有變量
複製代碼
當預編譯的時候,其實是將上面的代碼轉化爲:github
var b; // 全局變量 b 被提高到全局做用於頂部進行了聲明
function temp (conditionP { var a; // 局部變量 a 被提高到函數做用於頂部進行了聲明 if (condition) {
a = 1;
} else {
console.log(a); // undefined
}
}
b = 2;
複製代碼
同時因爲變量提高的緣由,即使在 temp
中 condition
爲 false
,在其 else
分支內仍然能夠訪問到 a
。數組
因爲變量提高的存在,對於接觸 JavaScript 的人來講不免會不太習慣,甚至會致使一些 bug。所以 ES6 加入了塊級做用域來對變量的聲明週期進行控制。網絡
ES6 的塊級聲明做用域指定的區塊之間,主要有:app
let
的用法與 var
相同,使用 let
進行聲明就能將變量的做用域限制在區塊中,不會進行變量提高。函數
若是上文的代碼中,a
使用 let
進行聲明的話,那麼在 if
語句中 condition
爲 false
時,else
分支將沒法訪問到 a
的值。工具
if (condition) {
let a = 1;
} else {
console.log(a); // Uncaught ReferenceError: a is not defined
}
複製代碼
在 同一個做用域內,不能使用 let
再次聲明一個已經存在的變量,不論該變量原先是用什麼關鍵字聲明的。
var a = 1;
let a = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared
複製代碼
一樣,若是是先使用 let
聲明,再使用其餘關鍵字聲明,一樣會報錯。
若是都是使用
var
關鍵字則不會報錯
若是是在當前做用域下內嵌另外一個做用域,則能夠在內嵌做用域中聲明與父級做用域同名的變量,不過在內嵌做用域中的變量在內嵌做用域內會覆蓋掉父級做用域的變量值。
由 const
聲明的是常量,常量一旦被賦值以後便不可改變。所以,每個常量在被聲明的同時 必須進行初始化。
const
與 let
相似,都不會產生變量提高,而且聲明的變量都只做用於域塊級做用域。同時,若是採用 const
聲明已被聲明的變量,一樣會報錯。
與 let
不一樣的是,不論嚴格模式或者非嚴格模式下,const
聲明過的變量都不能對其 值 進行改變。
與其餘的語言中的常量很不一樣的一點,const
聲明的常量雖然不可以改變值,但當使用 const
聲明對象時,能夠改變對象的屬性值。
const person = {
name: 'Jack',
};
person.name = 'Harry'; // 不會報錯
person = { name: 'Peter' }; // 報錯
複製代碼
在 JavaScript 中,對象是引用數據類型,即上面代碼中 person
存儲的實際上是對象的引用(引用能夠理解爲內存地址)。而對 person.name
的值的改變是改變 person
引用的對象,對 person
這個 引用 自己並未做出修改,所以使用 const
聲明的 person
並未報錯。
而當使用 { name: 'Peter' }
對 person
進行修改時,其實是將 person
的引用 更改 到新對象的內存地址,person
的值被改變了,所以報錯。
當使用 let
或者 const
聲明參數時,在預編譯階段,JavaScript 引擎會將這些參數綁定到其對應的做用域內,而且在執行到聲明語句以前若是對這些變量進行操做 均會報錯,即使是 typeof
。
咱們將變量在聲明以前所處的封閉區域成爲 臨時死區 或 暫時性死區 (Temporal Dead Zone,簡稱 TDZ)。
JavaScript 引擎在預編譯時,會將變量提高到做用域頂部(var
),或將變量放入 TDZ(let
或 const
)。訪問 TDZ 中的變量會報錯。當執行變量聲明語句後,變量纔會從 TDZ 中移除。
注意,TDZ 是綁定做用域的。若是在 TDZ 外訪問變量則不會報錯。如:
console.log(typeof param); // 輸出 'undefined',此處的 param 爲全局變量,預編譯的時候會有變量提高,所以是 undefined
// JavaScript 引擎在預編譯到 if 區塊時,會建立一個對應的 TDZ 而且把 param 加入其中
if (condition) {
param = 9; // 報錯,訪問了 if 區塊內的 TDZ 內的變量 param
let param = 1; // 此時 param 從 TDZ 中移出,能夠訪問
}
複製代碼
在 ES5 中,在循環內部聲明的變量,在循環外部仍舊能夠訪問。
for (var i = 0; i < 10; i += 1) {
console.log(i);
}
console.log(i); // 10;
複製代碼
這也是因爲變量提高, i
的聲明在預編譯時被提到全局做用域頂部,所以在循環結束後外部仍舊能夠訪問 i
。
若是採用 let
聲明的話,那麼 i
在循環結束後就會被銷燬,外部沒法進行訪問。
for (let i = 0; i < 10; i += 1) {
console.log(i);
}
console.log(i); // 報錯,沒法訪問 i
複製代碼
var funcs = [];
for (var i = 0; i < 10; i += 1) {
funcs.push(function () {
console.log(i);
});
}
funcs.forEach(function (func) {
func();
});
複製代碼
以上代碼執行後會輸出 10 個 10。這是因爲變量提高的緣由,i
提高到做用域的頂部,而且在循環外也可以訪問,在 for
循環執行完畢後,i
的值爲 10,所以在 forEach
遍歷數組內的函數而且執行時,均輸出 10。
在 ES5 中問了解決該問題,經常使用的方式是使用 當即調用函數表達式(IIFE)。具體寫法以下:
var funcs = [];
for (var i = 0; i < 10; i += 1) {
funcs.push((function (value) {
return function () {
console.log(value);
}
} (i)));
}
funcs.forEach(function (func) {
func(); // 0, 1, 2, ..., 10
});
複製代碼
在 IIFE 中,將 i
進行值傳遞(函數的傳參是值傳遞),建立副本而且存儲爲變量 value
,所以纔可以實現正確輸出。
在 ES6 中,能夠在 for
循環中直接使用 let
關鍵字聲明,來達到和 IIFE 同樣的效果。
for (let i = 0; i < 10; i+= 1) {
funcs.push(function () {
console.log(i);
})
}
複製代碼
在 for
循環中,聲明賦值語句 let i = 0
僅在循環以前執行一次(而且這一句執行的時候是在 父級 做用域,與循環內部的做用域是分開的),在執行循環時, JavaScript 內部會記錄當前循環的值,在進入下一輪時,會 建立一個新的值,而且用記住的值進行計算而且進入下一輪。所以使用 let
關鍵詞聲明的循環在每一次循環時都會獲得一個屬於該次循環的 副本。
for-in
語句使用 let
關鍵字是也是一樣的效果。
let
聲明在循環內部的表現是專門定義的,不必定與不產生變量提高的特性相關。
// 下面會報錯
for (const i = 0; i < 10; i += 1) {
...
}
// 下面則不會報錯
for (const i in obj) {
...
}
複製代碼
對於 for
循環來講,每次循環執行完畢,都會去修改 i
的值,而後再建立循環體內塊級做用域的副本,所以在 for
循環用 const
聲明時會報錯。
而 for-in
與 for-of
在每次迭代時,是每次都會在 新的做用域內 執行 const
聲明,不會修改值,所以不會報錯。
使用 var
進行全局變量聲明時,會爲全局對象建立一個新的屬性。以 Web 爲例:
var apple = 1;
console.log(window.apple); // 1
複製代碼
若是全局對象已經存在屬性,則會進行 覆蓋(這是一個隱患)。
使用 let
與 const
在全局做用域聲明時,會建立一個新的綁定,而且不會覆蓋全局對象已有的屬性。
let RepExp = 'Hello';
console.log(RepExp); // Hello
console.log(window.RepExp === RepExp); // false
複製代碼
在 ESLint 等代碼規範工具中,推薦的寫法是 默認使用 const
,在確實須要改變值或者引用的時候才使用 let
。雖然比較繁瑣,可是可以有效下降一些隱性 bug 出現的機率。
詳見 ESLint 的 no-var 與 prefer-const 規則