ES6之let(理解闭包)和const命令
ES6之let(理解閉包)和const命令
最近做項目的過程中,使用到了ES6,因為之前很少接觸,所以使用起來還不夠熟悉。因此購買了阮一峰老師的ES6標準入門,在此感謝阮一峰老師的著作。
我們知道,ECMAScript 6即ES6是ECMAScript的第五個版本,因為在2015年6月正式發布,所以又成為ECMAScript2015。ES6的主要目的是為了是JS用于編寫復雜的大型應用程序,成為企業級的開發語言。
說明:由于有時候我們希望得知es6代碼的具體實現原理或者說希望能夠轉化為es5使用,我們可以使用http://babeljs.io/來實現在線將es6代碼轉化為es5代碼。
第一部分:let命令
一.塊級作用域(重點)。
我們知道,在javascript中只有全局作用域和函數作用域,并不存在塊級作用域。這樣,在使用時就會出現一些問題。 下面我們先來舉例說明let塊級作用域的使用。
例1:
代碼如下所示:
????????{????????????var?a=5;let?b=10;}console.log(a);console.log(b);我們在控制臺得到的結果如下所示:
也就是說,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.這是為什么呢?阮一峰老師在書中的解釋并不是很清楚,所以下面我會發表個人見解:
? 關于這個問題,表面上確實不是很好理解,查詢了很多資料,許多人講到了很多晦澀難懂的知識,似乎很高大上,但是實際上并不難,下面根據我的理解進行解釋,如有問題,歡迎批評指正,如果大家能夠有些收獲就再好不過了。
?
?
例二前者(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規定暫時性死區和不存在變量提升就是為了減少運行時的錯誤,防止在變量聲明前就使用這個變量,從而導致意料之外的行為。
?
四.不允許重復聲明
????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所指向的地址不可變,但是地址的內容是可以變得。
轉載于:https://blog.51cto.com/zhanglida66/1921426
總結
以上是生活随笔為你收集整理的ES6之let(理解闭包)和const命令的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用hexo搭建个人博客
- 下一篇: C++ boost thread学习(二