ES6 系列之 let 和 const

塊級作用域的湧現

經由歷程 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 節:

《ES6 系列之 let 和 const》

我們會發明,在 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,對作者也是一種勉勵。

    原文作者:冴羽
    原文地址: https://segmentfault.com/a/1190000014951691
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞