ES6之let(理解閉包)和const命令

ES6之let(理解閉包)和const命令

  最近作項目的過程當中,使用到了ES6,由於以前不多接觸,因此使用起來還不夠熟悉。所以購買了阮一峯老師的ES6標準入門,在此感謝阮一峯老師的著做。javascript

  咱們知道,ECMAScript 6即ES6是ECMAScript的第五個版本,由於在2015年6月正式發佈,因此又成爲ECMAScript2015。ES6的主要目的是爲了是JS用於編寫複雜的大型應用程序,成爲企業級的開發語言。html

  說明:因爲有時候咱們但願得知es6代碼的具體實現原理或者說但願可以轉化爲es5使用,咱們可使用http://babeljs.io/來實如今線將es6代碼轉化爲es5代碼。java

第一部分:let命令

  一.塊級做用域(重點)。

   咱們知道,在javascript中只有全局做用域和函數做用域,並不存在塊級做用域。這樣,在使用時就會出現一些問題。 下面咱們先來舉例說明let塊級做用域的使用。es6

   例1:數組

  代碼以下所示: 安全

        {
            var a=5;
            let b=10;
        }
        console.log(a);
        console.log(b);

  咱們在控制檯獲得的結果以下所示:babel

  也就是說,var聲明的變量因爲不存在塊級做用域因此能夠在全局環境中調用,而let聲明的變量因爲存在塊級做用域因此不能在全局環境中調用。閉包

 

  例2:這個例子是一個很是經典的例子。函數

  

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

 

  咱們能夠看到,兩個例子中,惟一的區別是前者for循環中使用var來定義i,獲得的結果是10.然後者使用的是let來定義i,最終獲得的結果是6.這是爲何呢?阮一峯老師在書中的解釋並非很清楚,因此下面我會發表我的看法:spa

    關於這個問題,表面上確實不是很好理解,查詢了不少資料,許多人講到了不少晦澀難懂的知識,彷佛很高大上,可是實際上並不難,下面根據個人理解進行解釋,若有問題,歡迎批評指正,若是你們可以有些收穫就再好不過了。

 

 

  例二前者(var i)具體執行過程以下:

var a=[];


var i=0;//因爲var來聲明變量i,因此for循環代碼塊不具有塊級做用域,所以i認爲是全局變量,直接放在全局變量中。
a[0]=function(){
    console.log(i);//這裏之因此i爲i而不是0;是由於咱們只是定義了該函數,未被調用,因此沒有進入該函數執行環境,i固然不會沿着做用域鏈向上搜索找到i的值。
}// 因爲不具有塊級做用域,因此該函數定義就是全局做用域。


var i=1;//第二次循環,這時var i=1;覆蓋了前面的var i=0;即如今i爲1;
a[1]=function(){
    console.log(i);//解釋同a[0]函數。
}


var i=2;// 第三次循環,這時 i=2,在全局做用域中,因此覆蓋了前面的i=1;
a[2]=function(){
    console.log(i);
}


......第四次循環 此時i=3  這個以及下面的i不斷的覆蓋前面的i,由於都在全局做用域中
......第五次循環 此時i=4
......第六次循環 此時i=5
......第七次循環 此時i=6
......第八次循環 此時i=7
......第九次循環 此時i=8   


var i=9;
a[9]=function(){
    console.log(i);
}


var i=10;// 這時i爲10,由於不知足循環條件,因此中止循環

緊接着在全局環境中繼續向下執行。

       a[6]();//這時調用a[6]函數,因此這時隨即進入a[6]函數的執行環境,即a[6]=function(){console.log(i)};執行函數中的代碼 console.log(i); 由於在函數執行環境中不存在變量i,因此此時會沿着做用域鏈向上尋找(可參考個人博文《深刻理解做用域和做用域鏈,即進入了全局做用域中尋找變量i,而全局做用域中i=10覆蓋了前面全部的i值,因此說這時i爲10,那麼a[6]的值就是10了。

  

  說明:對於例如a[1]=function(){console.log(i)};而不是a[1]=function{console.log(1)},能夠在控制檯中輸出a[1]函數,便可獲得驗證。

 

 

     例二後者(let i)具體執行過程以下:

 


var a=[];//建立一個數組a;

{ //進入第一次循環
    let i=0; //注意:由於使用let使得for循環爲塊級做用域,這次let i=0在這個塊級做用域中,而不是在全局環境中。
    a[0]=function(){
        console.log(i);
     }; //注意:因爲循環時,let聲明i,因此整個塊是塊級做用域,那麼a[0]這個函數就成了一個閉包。
}// 聲明: 我這裏用{}表達並不符合語法,只是但願經過它來講明let存在時,這個for循環塊是塊級做用域,而不是全局做用域。

 

講道理,上面這是一個塊級做用域,就像函數做用域同樣,函數執行完畢,其中的變量會被銷燬,可是由於這個代碼塊中存在一個閉包,閉包的做用域鏈中包含着(或着說是引用着)塊級做用域,因此在閉包被調用以前,這個塊級做用域內部的變量不會被銷燬。(更多閉包知識,能夠看個人博文《JavaScript之閉包》)


{ //進入第二次循環
     let i=1; //注意:由於let i=1; 和 上面的let i=0;出在不一樣的做用域中,因此二者不會相互影響。
     a[1]=function(){
         console.log(i);
     }; //一樣,這個a[i]也是一個閉包
}

......進入第三次循環,此時其中let i=2;
......進入第四次循環,此時其中let i=3;
......進入第五次循環,此時其中let i=4;
......進入第六次循環,此時其中let i=5;
......進入第七次循環,此時其中let i=6;
......進入第八次循環,此時其中let i=7;
......進入第九次循環,此時其中let i=8;

{//進入第十次循環
    let i=9;
    a[i]=function(){
        console.log(i);
    };//一樣,這個a[i]也是一個閉包
}

{
    let i=10;//不符合條件,再也不向下執行。因而這個代碼塊中不存在閉包,let i=10;在此次循環結束以後難逃厄運,隨即被銷燬。
}

a[6]();//調用a[6]()函數,這時執行環境隨即進入下面這個代碼塊中的執行環境:funcion(){console.log(i)};

{
     let i=6;
     a[6]=function(){
          console.log(i);
     }; //一樣,這個a[i]也是一個閉包
}

    a[6]函數(閉包)這個執行環境中,它會首先尋找該執行環境中是否存在 i,沒有找到,就沿着做用域鏈繼續向上到了其所在的代碼塊執行環境,找到了i=6,因而輸出了6,即a[6]();的結果爲6。這時,閉包被調用,因此整個代碼塊中的變量i和函數a[6]()被銷燬。

 

相信你們仔細看完上面的函數執行的過程,對let var 塊級做用域 閉包就有一個很好的理解了。我認爲重要的是對於函數執行過程的理解!

 

 

  二.不存在變量提高

  這裏是說使用let不會像使用var同樣存在一個變量提高的現象。變量提高是什麼呢?在沒有接觸es6以前我對此也不清楚,可是我想你們必定都據說過函數聲明提高:函數聲明來定義函數便可實現函數聲明提高,這樣,咱們能夠先調用函數,後聲明函數;而函數表達式方法不會實現函數聲明提高,這樣,若是先調用函數,後聲明函數,則會拋出錯誤!!(對於函數聲明提高更多知識能夠看個人博文《JavaScript函數之美~》)。  那麼能夠以此類推,var定義變量:能夠先使用,後聲明;而let定義變量:只可先聲明,後使用。

  例3:

  

        var num1=100;
        console.log(num1);

        let num2=200;
        console.log(num2);

        console.log(i);
        var i=10;

        console.log(j);
        let j=5;

    咱們能夠看到結果以下:

即前兩個都是先聲明後使用,沒有問題。然後兩個都是先使用,後聲明,用var 聲明的顯示undefined,而 let聲明的直接報錯。

說明:console.log(i);

   var i=10;

實際上至關於:

    var i;

    console.log(i);

   i=10;

因此會出現undefined的狀況。

 

 

  三.暫時性死區

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

例5:

 

    var tmp=123;
    if(true){
        tmp="abc";
        let tmp;
    }

 

   結果以下:

也就是說:雖然上面的代碼中存在全局變量tmp,可是塊級做用域內let又聲明瞭一個局部變量tmp,致使後者綁定了塊級做用域,因此在let聲明變量前,對tmp賦值會報錯。此即暫時性死區。

  

  注意:ES6規定暫時性死區和不存在變量提高就是爲了減小運行時的錯誤,防止在變量聲明前就使用這個變量,從而致使意料以外的行爲。

  

  暫時性死區就是: 只要塊級做用域內存在let,那麼他所聲明的變量就綁定了這個區域,再也不受外部的影響。

  暫時性死區即 Temperary Dead Zone,即TDZ。 

  

       注意:暫時性死區也意味着 typeof 再也不是一個百分之百安全的操做。  以下:

    if (true) {
      console.log(typeof x);
      let x;
    }

  這裏若是沒有let x,那麼typeof x的結果是 undefined,可是若是使用了let x,由於let不存在變量提高,因此這裏造成了暫時性死區,即typeof x也是會報錯的。。。  從這裏能夠理解暫時性死區實際上就是這一部分是有問題的 。

 

 

 

  四.不容許重複聲明

  

    function func (){
        let b=100;
        var b=10;
    }

    function add(num){
        let num;
        return num+1;
    }

    function another(){
        let a=10;
        let a=5;
    }

上述三個獲得的結果均爲:

只是前二者爲 b和num被聲明過了。注意:第二個函數,雖然咱們沒有明確的聲明,可是參數其實是至關於用var聲明的局部變量。

 

 

 

第二部分:const命令

      什麼使const命令呢?實際上它也是一種聲明常量的方式。const命令用來聲明常量,一旦聲明,其值就不能改變。初次以外,const和let十分類似。也就是說前者是用於聲明常量的,後者是用於聲明變量的。

 

    1.const聲明常量,一旦聲明,不可改變。

    const a=10;
    a=100;

    結果以下

    

    2.既然const一旦聲明不可改變,因此在聲明時必須初始化。

const a;

     結果以下:

 

    3.const所在的代碼塊爲塊級做用域,因此其變量只在塊級做用域內使用或其中的閉包使用。

    if(true){
        const a=10;
    }
    console.log(a);

    結果以下:

 

    

    4.const聲明的變量不存在變量提高。

    if(true){
        console.log(a);
        const a=10;
    }

結果以下:

  

 

    5.const不可重複聲明常量。

    var a=10;
    const a=5;

結果以下:

 

 

    6.const命令只是保證了變量名指向的地址不變,並不保證該地址的數據不變。

  

    const a={};
    a.name="zzw";
    console.log(a.name); 

    const b=[];
    b.push("zzw");
    console.log(b);

    const c={};
    c={name:"zzw"};

  結果以下:

所以,咱們使用const所指向的地址不可變,可是地址的內容是能夠變得。

 

    7.若是但願將對象自己凍結,可使用Object.freeze()方法。

    const a=Object.freeze({});
    a.name="zzw";
    console.log(a.name); //undefined

    因而經過Object.freeze()方法咱們就不能夠再改變對象的屬性了(無效)。

相關文章
相關標籤/搜索