重讀《深入理解ES6》 —— 塊級作用域

李等等扣丁發表於2019-02-27

一、關於變數宣告提升

在 ES5 中,我們通常會使用 var 來宣告變數。在使用 var 宣告變數的時候,通常會遇到變數宣告提升的問題。這種機制會讓很多初學者疑惑不解。其實當我們理解了一個變數通常包括宣告賦值兩個部分,這個問題也就不難理解了。

    // 1
    console.log(a); // undefined
    var a = 3;
    
    // 2
    function foo() {
        console.log(b);  // undefined
        if (true) {
            var b = 2;
        }
    }
複製程式碼

事實上,無論在全域性作用域或者函式作用域中,只要通過 var 關鍵字宣告的變數,不論在哪裡宣告,都會被當成在當前作用域頂部宣告的變數。

    // 1
    var a;
    console.log(a);
    a = 3;
    
    // 2
    function foo() {
    	var b;
        console.log(b);
        if (true) {
            b = 2;
        }
    }
複製程式碼

二、塊級作用域

其實,變數提升的機制,不太符合我們的編碼習慣,我們常常希望程式碼能夠按照順序執行,這也符合一般人的邏輯習慣。為此 ES6 引入了塊級作用域的概念。

塊級作用域其實就是詞法作用域,我們的程式碼寫在哪,就會在哪裡執行,這更符合我們的程式設計習慣。我們常說的塊包括函式內部{}之間的部分

為了實現塊級作用域,ES6 採用 letconst 代替 var 來宣告變數。用 letconst 宣告的變數會把變數的作用域限制在當前的程式碼塊中,並且宣告的變數不會被提升。另外,用 let 宣告的變數,在同一程式碼塊內,禁止重複宣告。

    // 1、變數不會提升
    console.log(a); // ReferenceError: a is not defined
    let a = 3;
    
    // 2、變數只能在當前作用域訪問
    if (true) {
        const b = 3;
        console.log(b); // 3
    }
    console.log(b); // ReferenceError: b is not defined
    
    // 3、禁止重複宣告
    function foo() {
        let c = 3;
        let c = 4; // Identifier 'c' has already been declared
    }
    foo();
複製程式碼

三、let 與 const 的區別

letconst 都可以建立一個塊級作用域,唯一的區別是 const 用來宣告一個常量,它的值一旦被設定後不可修改。所以,用 const 宣告的常量必須初始化。

    // 1、不可更改
    const a = 1;
    a = 2; // TypeError: Assignment to constant variable.
    
    // 2、必須初始化
    const b; // SyntaxError: Missing initializer in const declaration
複製程式碼

關於 const 宣告的變數不可修改,有一個值得注意的地方就是用 const 宣告一個物件。比如:

    const tom = {
        age: 18,
        city: 'shanghai'
    };
    
    tom.age = 19; // 這是可以的
複製程式碼

我們可以理解為,用 const 宣告瞭一個變數 tom,將一個物件的引用地址賦值給變數 tom,只要這個引用地址不發生變化,內部的值是可以修改的。

四、迴圈中塊級作用域

在 ES5 中,比較讓人頭疼的地方可能就是 for 迴圈了。在迴圈中,我們用 var 宣告一個變數,迴圈結束後,我們其實是希望這個變數被銷燬的。但由於 var 宣告的變數具有宣告提升的特性,所以當我們用 for 迴圈的時候,往往會汙染我們的全域性作用域。

    for (var i = 0; i < 10; i++) {
        // do something
    }
    console.log(i); // 10
    
    // 當迴圈結束的時候,其實我們是希望變數 i 可以被銷燬的。
    // 但其實它被留在了全域性
複製程式碼

這個時候,我們使用 let 來宣告迴圈中的變數,就可以輕易的解決這個問題。

    for (let i = 0; i < 10; i++) {
        // do something
    }
    console.log(i); // ReferenceError: i is not defined
    
    // 可以看到,迴圈結束,變數 i 就被銷燬了。 perfect~~
複製程式碼

最後總結一下,letconst 幫助我們解決了不少問題,我們不會再為變數提升引發的種種問題而困惑了,同時在迴圈中使用 let 來代替 var 可以在迴圈結束的時候銷燬變數,避免無用的變數影響全域性。而當前使用塊級繫結的最佳實踐是:預設使用 const ,只在確定需要改變變數的值時,使用 let ,以最大化地避免錯誤的產生。

如果文章中有錯誤或表述不嚴謹的地方,歡迎指正。

也歡迎大家關注我的同名微信公眾號:李等等扣丁

重讀《深入理解ES6》 —— 塊級作用域

相關文章