let和const命令

1、let命令數組

  一、基本語法數據結構

  ES6新增了let命令,用於聲明變量。其語法與var相似,可是所聲明的變量只在let命令所在的代碼塊內有效。函數

{
    let a = 1;
    var b = 2;
}
console.log(a); // ReferenceError: a is not defined
console.log(b); // 2

   上面的代碼在代碼塊中分別用let和var聲明瞭兩個變量,而後在代碼塊以外調用這兩個變量,結果let聲明的變量報錯,var聲明的輸出了正確的值。這代表,let聲明的變量只在其所在的代碼塊內有效。spa

   下面來個經典的例子:指針

// var
{
    var arr = [];
    for (var i = 0; i < 10; i++) {
        arr[i] = function() {
            console.log(i);
        };
    }
    arr[3](); // 10
}

  上面的代碼中,變量i是var聲明的,在全局範圍內都有效,因此全局只有一個變量i。每一次循環,變量i的值都會發生改變,而循環內,被賦給數組arr的函數內部的console.log(i)中的i指向全局中的i。也就是說,全部數組arr的成員中的i指向的都是同一個i,致使運行時輸出的是最後一輪的i值,也就是10。code

// let
{
    var arr = [];
    for (let i = 0; i < 10; i++) {
        arr[i] = function() {
            console.log(i);
        };
    }
    arr[3](); // 3
}

   上面的代碼中,變量是let聲明的,當前的i只在本輪循環有效。因此每一次循環的i其實都是一個新的變量,因而最後輸出的是3。你們可能會問,若是每一輪循環變量i都是從新聲明的,那它怎麼知道上一輪循環的值從而計算出本輪循環的值呢?這是由於JavaScript引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算。對象

   另外,for循環還有一個特別之處,就是設置循環變量的那一部分是一個父做用域,而循環體內部是一個單獨的子做用域。blog

{
    for (let i = 0; i < 2; i++) {
        let i = 'SophiaLee';
        console.log(i);
    }
    // SophiaLee
    // SophiaLee
}

  二、不存在變量提高ip

  var命令會發生「變量提高」的現象,即變量能夠在聲明以前使用,值爲undefined。這種現象多少是有些奇怪的,按照通常的邏輯,變量應該在聲明語句以後才能使用。內存

  爲了糾正這種現象,let命令改變了語法行爲,它所聲明的變量必定要在聲明後使用,不然會報錯。

// var
{
    console.log(a); // undefined
    var a = 5;
}
// let
{
    console.log(b); // ReferenceError: b is not defined
    let b = 1;
}

  在上面的代碼中,變量a用var命令聲明會發生變量提高,當腳本開始運行時,變量a就已經存在,可是沒有值,因此輸出undefined。變量b用let命令聲明則不會發生變量提高。這代表在聲明它以前,變量b是不存在的,這時若是用到它,就會拋出錯誤。

  三、暫時性死區

  只要塊級做用域內存在let命令,它所聲明的變量就「綁定」這個區域,再也不受外部的影響。

  ES6明確規定,若是區塊中存在let和const命令,則這個區域對這些命令聲明的變量從一開始就造成封閉的做用域。只要在聲明以前使用這些變量,就會報錯。總之,在代碼塊內,使用let命令聲明變量以前,該變量都是不可用的。這在語法上成爲「暫時性死區(TDZ)」。

// 暫時性死區
{
    if (true) {
        // TDZ開始
        a = 1;
        console.log(a); // ReferenceError: a is not defined
        let a; // TDZ結束
        console.log(a); // undefined
        a = 2;
        console.log(a); // 2
    }
}

  上面的代碼中,在let命令聲明變量a以前,都屬於變量a的「死區」。

  另外,有些「死區」比較隱蔽,不太容易發現。

{
    function a(x = y, y = 2) {
        return [x, y];
    }
    a();  // ReferenceError: y is not defined
}

  上面的代碼中,調用a函數之因此報錯,是由於參數x的默認值等於另外一個參數y,而此時y尚未聲明,屬於「死區」。若是y的默認值是x,就不會報錯。

{
    function a(x = 1, y = x) {
        return [x, y];
    }
    a();  // [1, 1]
}
{
    // 不報錯
    var x = x;
    // 報錯
    let x = x; // ReferenceError: x is not defined    
} 

  總之,暫時性死區的本質就是,只要進入當前做用域,所要使用的變量已經存在,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。

  四、不容許重複聲明

  let不容許在同一做用域內重複聲明同一個變量。

{
    // 報錯
    let a = 1;
    // var a = 2;
}
{
    function a(arg) {
        let arg;
    }
    a(); // SyntaxError: Identifier 'arg' has already been declared
}
{
    function a(arg) {
        {
            let arg; // 不報錯
        }
    }
    a();
}

2、const命令

  一、基本用法

  const聲明一個只讀的常量。一旦聲明,常量的值就不能改變。

{
    const PI = 3.1415926;
    console.log(PI); // 3.1415926

    PI = 4; // TypeError: Assignment to constant variable.
}

  上面的代碼說明改變常量的值會報錯。

  const聲明的變量不得改變值,這意味着,const一旦聲明常量,就必須當即初始化,不能留到後面賦值。(只聲明不賦值會報錯)

  const的做用域與let命令相同:只在聲明所在的塊級做用域內有效。

  const命令聲明的變量也不會提高,一樣存在暫時性死區,只能在聲明以後使用。

  二、本質

  const實際上保證的並非變量的值不得改動,而是變量指向的那個內存地址不得改動。對於簡單類型的數據(Number、String、Boolean)而言,值就保存在變量指向的內存地址中,所以等同於常量。但對於複合類型的數據(主要是對象和數組)而言,變量指向的內存地址保存的只是一個指針,const只能保證這個指針是固定的,至於它指向的數據結構是不可變的,這徹底不能控制。所以,將一個對象聲明爲常量時必須很是當心。

{
    const person = {};
    // 爲person添加一個屬性,能夠成功
    person.name = 'SophiaLee';
    console.log(person.name); // SophiaLee

    // 將person指向另外一個對象,就會報錯
    person = {}; // TypeError: Assignment to constant variable. 類型錯誤:賦值給常量變量

}

  上面的代碼中,常量person儲存的是一個地址,這個地址指向的是一個對象。不可變的只是這個地址,即不能把person指向另外一個地址,但對象自己是可變的,因此依然能夠爲其添加新屬性。

  再看另外一個例子:

{
    const arr = [];
    arr.push('Hello'); // 可執行
    arr.length = 0; // 可執行
    arr = ['World']; // 報錯 TypeError: Assignment to constant variable.
}

  上面的代碼中,常量arr是一個數組,這個數組自己是可寫的,可是若是將另外一個數組賦值給arr,就會報錯。

相關文章
相關標籤/搜索