塊級作用域的湧現
經由歷程 var 聲明的變量存在變量提拔的特徵:
if (condition) {
var value = 1;
}
console.log(value);
初學者能夠會以為只要 condition 為 true 的時刻,才會豎立 value,假如 condition 為 false,效果應當是報錯,但是由於變量提拔的緣由,代碼相當於:
var value;
if (condition) {
value = 1;
}
console.log(value);
假如 condition 為 false,效果會是 undefined。
除此以外,在 for 輪迴中:
for (var i = 0; i < 10; i++) {
...
}
console.log(i); // 10
即使輪迴已完畢了,我們依舊能夠接見 i 的值。
為了加強對變量生命周期的掌握,ECMAScript 6 引入了塊級作用域。
塊級作用域存在於:
- 函數內部
- 塊中(字符 { 和 } 之間的地區)
let 和 const
塊級聲明用於聲明在指定塊的作用域以外無法接見的變量。
let 和 const 都是塊級聲明的一種。
我們來回憶下 let 和 const 的特性:
1.不會被提拔
if (false) {
let value = 1;
}
console.log(value); // Uncaught ReferenceError: value is not defined
2.反覆聲明報錯
var value = 1;
let value = 2; // Uncaught SyntaxError: Identifier 'value' has already been declared
3.不綁定全局作用域
當在全局作用域中運用 var 聲明的時刻,會豎立一個新的全局變量作為全局對象的屬性。
var value = 1;
console.log(window.value); // 1
但是 let 和 const 不會:
let value = 1;
console.log(window.value); // undefined
再來講下 let 和 const 的區分:
const 用於聲明常量,其值一旦被設定不能再被修正,不然會報錯。
值得一提的是:const 聲明不許可修正綁定,但許可修正值。這意味着當用 const 聲明對象時:
const data = {
value: 1
}
// 沒有題目
data.value = 2;
data.num = 3;
// 報錯
data = {}; // Uncaught TypeError: Assignment to constant variable.
暫時死區
暫時死區(Temporal Dead Zone),簡寫為 TDZ。
let 和 const 聲明的變量不會被提拔到作用域頂部,假如在聲明之前接見這些變量,會致使報錯:
console.log(typeof value); // Uncaught ReferenceError: value is not defined
let value = 1;
這是由於 JavaScript 引擎在掃描代碼發明變量聲明時,要麼將它們提拔到作用域頂部(碰到 var 聲明),要麼將聲明放在 TDZ 中(碰到 let 和 const 聲明)。接見 TDZ 中的變量會觸發運行時毛病。只要實行過變量聲明語句后,變量才會從 TDZ 中移出,然後方可接見。
看似很好明白,不保證你不出錯:
var value = "global";
// 例子1
(function() {
console.log(value);
let value = 'local';
}());
// 例子2
{
console.log(value);
const value = 'local';
};
兩個例子中,效果並不會打印 “global”,而是報錯 Uncaught ReferenceError: value is not defined
,就是由於 TDZ 的原因。
輪迴中的塊級作用域
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 3
一個陳詞濫調的面試題,解決計劃以下:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(i){
return function() {
console.log(i);
}
}(i))
}
funcs[0](); // 0
ES6 的 let 為這個題目供應了新的解決方法:
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 0
題目在於,上面講了 let 不提拔,不能反覆聲明,不能綁定全局作用域等等特徵,但是為何在這裏就可以準確打印出 i 值呢?
假如是不反覆聲明,在輪迴第二次的時刻,又用 let 聲清楚明了 i,應當報錯呀,就算由於某種緣由,反覆聲明不報錯,一遍一遍迭代,i 的值終究照樣應當是 5 呀,另有人說 for 輪迴的
設置輪迴變量的那部份是一個零丁的作用域,就比方:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
這個例子是對的,假如我們把 let 改成 var 呢?
for (var i = 0; i < 3; i++) {
var i = 'abc';
console.log(i);
}
// abc
為何效果就不一樣了呢,假如有零丁的作用域,效果應當是雷同的呀……
假如要追查這個題目,就要揚棄掉之前所講的這些特徵!這是由於 let 聲明在輪迴內部的行動是範例中特地定義的,不一定就與 let 的不提拔特徵有關,實在,在初期的 let 完成中就不包括這一行動。
我們檢察 ECMAScript 範例第 13.7.4.7 節:
我們會發明,在 for 輪迴中運用 let 和 var,底層會運用差別的處置懲罰方式。
那末當運用 let 的時刻底層究竟是怎樣做的呢?
簡樸的來講,就是在 for (let i = 0; i < 3; i++)
中,即圓括號以內豎立一個隱蔽的作用域,這就可以夠詮釋為何:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
然後每次迭代輪迴時都豎立一個新變量,並以之前迭代中同名變量的值將其初始化。如許關於下面如許一段代碼
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 0
就相當於:
// 偽代碼
(let i = 0) {
funcs[0] = function() {
console.log(i)
};
}
(let i = 1) {
funcs[1] = function() {
console.log(i)
};
}
(let i = 2) {
funcs[2] = function() {
console.log(i)
};
};
當實行函數的時刻,依據詞法作用域就可以夠找到準確的值,實在你也能夠明白為 let 聲明模仿了閉包的做法來簡化輪迴歷程。
輪迴中的 let 和 const
不過到這裏還沒有完畢,假如我們把 let 改成 const 呢?
var funcs = [];
for (const i = 0; i < 10; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // Uncaught TypeError: Assignment to constant variable.
效果會是報錯,由於雖然我們每次都豎立了一個新的變量,但是我們卻在迭代中嘗試修正 const 的值,所以終究會報錯。
說完了一般的 for 輪迴,我們另有 for in 輪迴呢~
那下面的效果是什麼呢?
var funcs = [], object = {a: 1, b: 1, c: 1};
for (var key in object) {
funcs.push(function(){
console.log(key)
});
}
funcs[0]()
效果是 ‘c’;
那假如把 var 改成 let 或許 const 呢?
運用 let,效果天然會是 ‘a’,const 呢? 報錯照樣 ‘a’?
效果是準確打印 ‘a’,這是由於在 for in 輪迴中,每次迭代不會修正已有的綁定,而是會豎立一個新的綁定。
Babel
在 Babel 中是怎樣編譯 let 和 const 的呢?我們來看看編譯后的代碼:
let value = 1;
編譯為:
var value = 1;
我們能夠看到 Babel 直接將 let 編譯成了 var,假如是如許的話,那末我們來寫個例子:
if (false) {
let value = 1;
}
console.log(value); // Uncaught ReferenceError: value is not defined
假如照樣直接編譯成 var,打印的效果肯定是 undefined,但是 Babel 很智慧,它編譯成了:
if (false) {
var _value = 1;
}
console.log(value);
我們再寫個直觀的例子:
let value = 1;
{
let value = 2;
}
value = 3;
var value = 1;
{
var _value = 2;
}
value = 3;
實質是一樣的,就是轉變量名,使內外層的變量稱號不一樣。
那像 const 的修正值時報錯,以及反覆聲明報錯怎樣完成的呢?
實在就是在編譯的時刻直接給你報錯……
那輪迴中的 let 聲明呢?
var funcs = [];
for (let i = 0; i < 10; i++) {
funcs[i] = function () {
console.log(i);
};
}
funcs[0](); // 0
Babel 奇妙的編譯成了:
var funcs = [];
var _loop = function _loop(i) {
funcs[i] = function () {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
funcs[0](); // 0
最好實踐
在我們開闢的時刻,能夠以為應當默許運用 let 而不是 var ,這類情況下,關於須要寫保護的變量要運用 const。但是另一種做法日趨提高:默許運用 const,只要當確切須要轉變變量的值的時刻才運用 let。這是由於大部份的變量的值在初始化后不應再轉變,而預感以外的變量之的轉變是許多 bug 的泉源。
ES6 系列
ES6 系列目次地點:https://github.com/mqyqingfen…
ES6 系列估計寫二十篇擺布,旨在加深 ES6 部份知識點的明白,重點解說塊級作用域、標籤模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模仿完成、模塊加載計劃、異步處置懲罰等內容。
假如有毛病或許不嚴謹的處所,請務必賦予斧正,非常謝謝。假如喜好或許有所啟示,迎接star,對作者也是一種勉勵。