ES6之let(理解闭包)和const命令
ES6之let(理解閉包)和const命令
最近做項目的過程中,使用到了ES6,因為之前很少接觸,所以使用起來還不夠熟悉。因此購買了阮一峰老師的ES6標(biāo)準(zhǔn)入門,在此感謝阮一峰老師的著作。
我們知道,ECMAScript 6即ES6是ECMAScript的第五個版本,因為在2015年6月正式發(fā)布,所以又成為ECMAScript2015。ES6的主要目的是為了是JS用于編寫復(fù)雜的大型應(yīng)用程序,成為企業(yè)級的開發(fā)語言。
說明:由于有時候我們希望得知es6代碼的具體實現(xiàn)原理或者說希望能夠轉(zhuǎn)化為es5使用,我們可以使用http://babeljs.io/來實現(xiàn)在線將es6代碼轉(zhuǎn)化為es5代碼。
第一部分:let命令
一.塊級作用域(重點(diǎn))。
我們知道,在javascript中只有全局作用域和函數(shù)作用域,并不存在塊級作用域。這樣,在使用時就會出現(xiàn)一些問題。 下面我們先來舉例說明let塊級作用域的使用。
例1:
代碼如下所示:
????????{????????????var?a=5;let?b=10;}console.log(a);console.log(b);我們在控制臺得到的結(jié)果如下所示:
也就是說,var聲明的變量由于不存在塊級作用域所以可以在全局環(huán)境中調(diào)用,而let聲明的變量由于存在塊級作用域所以不能在全局環(huán)境中調(diào)用。
例2:這個例子是一個非常經(jīng)典的例子。
????????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
?
我們可以看到,兩個例子中,唯一的區(qū)別是前者for循環(huán)中使用var來定義i,得到的結(jié)果是10.而后者使用的是let來定義i,最終得到的結(jié)果是6.這是為什么呢?阮一峰老師在書中的解釋并不是很清楚,所以下面我會發(fā)表個人見解:
? 關(guān)于這個問題,表面上確實不是很好理解,查詢了很多資料,許多人講到了很多晦澀難懂的知識,似乎很高大上,但是實際上并不難,下面根據(jù)我的理解進(jìn)行解釋,如有問題,歡迎批評指正,如果大家能夠有些收獲就再好不過了。
?
?
例二前者(var i)具體執(zhí)行過程如下:
var a=[];
var i=0;//由于var來聲明變量i,所以for循環(huán)代碼塊不具備塊級作用域,因此i認(rèn)為是全局變量,直接放在全局變量中。
a[0]=function(){
? ? console.log(i);//這里之所以i為i而不是0;是因為我們只是定義了該函數(shù),未被調(diào)用,所以沒有進(jìn)入該函數(shù)執(zhí)行環(huán)境,i當(dāng)然不會沿著作用域鏈向上搜索找到i的值。
}// 由于不具備塊級作用域,所以該函數(shù)定義就是全局作用域。
var i=1;//第二次循環(huán),這時var i=1;覆蓋了前面的var i=0;即現(xiàn)在i為1;
a[1]=function(){
? ? console.log(i);//解釋同a[0]函數(shù)。
}
var i=2;// 第三次循環(huán),這時 i=2,在全局作用域中,所以覆蓋了前面的i=1;
a[2]=function(){
? ? console.log(i);
}
......第四次循環(huán) 此時i=3 ?這個以及下面的i不斷的覆蓋前面的i,因為都在全局作用域中
......第五次循環(huán) 此時i=4
......第六次循環(huán) 此時i=5
......第七次循環(huán) 此時i=6
......第八次循環(huán) 此時i=7
......第九次循環(huán) 此時i=8 ??
var i=9;
a[9]=function(){
? ? console.log(i);
}
var i=10;// 這時i為10,因為不滿足循環(huán)條件,所以停止循環(huán)。
緊接著在全局環(huán)境中繼續(xù)向下執(zhí)行。
? ? ? ?a[6]();//這時調(diào)用a[6]函數(shù),所以這時隨即進(jìn)入a[6]函數(shù)的執(zhí)行環(huán)境,即a[6]=function(){console.log(i)};執(zhí)行函數(shù)中的代碼 console.log(i); 因為在函數(shù)執(zhí)行環(huán)境中不存在變量i,所以此時會沿著作用域鏈向上尋找(可參考我的博文《深入理解作用域和作用域鏈》),即進(jìn)入了全局作用域中尋找變量i,而全局作用域中i=10覆蓋了前面所有的i值,所以說這時i為10,那么a[6]的值就是10了。
說明:對于例如a[1]=function(){console.log(i)};而不是a[1]=function{console.log(1)},可以在控制臺中輸出a[1]函數(shù),即可得到驗證。
?
?
? ?例二后者(let i)具體執(zhí)行過程如下:
?
var a=[];//創(chuàng)建一個數(shù)組a;
{ //進(jìn)入第一次循環(huán)
? ? let i=0; //注意:因為使用let使得for循環(huán)為塊級作用域,此次let i=0在這個塊級作用域中,而不是在全局環(huán)境中。
? ? a[0]=function(){
? ? ? ? console.log(i);
? ? ?}; //注意:由于循環(huán)時,let聲明i,所以整個塊是塊級作用域,那么a[0]這個函數(shù)就成了一個閉包。
}//?聲明:?我這里用{}表達(dá)并不符合語法,只是希望通過它來說明let存在時,這個for循環(huán)塊是塊級作用域,而不是全局作用域。
?
講道理,上面這是一個塊級作用域,就像函數(shù)作用域一樣,函數(shù)執(zhí)行完畢,其中的變量會被銷毀,但是因為這個代碼塊中存在一個閉包,閉包的作用域鏈中包含著(或著說是引用著)塊級作用域,所以在閉包被調(diào)用之前,這個塊級作用域內(nèi)部的變量不會被銷毀。(更多閉包知識,可以看我的博文《JavaScript之閉包》)
{ //進(jìn)入第二次循環(huán)
? ? ?let i=1; //注意:因為let i=1; 和?上面的let i=0;出在不同的作用域中,所以兩者不會相互影響。
? ? ?a[1]=function(){
? ? ? ? ?console.log(i);
? ? ?}; //同樣,這個a[i]也是一個閉包
}
......進(jìn)入第三次循環(huán),此時其中l(wèi)et i=2;
......進(jìn)入第四次循環(huán),此時其中l(wèi)et i=3;
......進(jìn)入第五次循環(huán),此時其中l(wèi)et i=4;
......進(jìn)入第六次循環(huán),此時其中l(wèi)et i=5;
......進(jìn)入第七次循環(huán),此時其中l(wèi)et i=6;
......進(jìn)入第八次循環(huán),此時其中l(wèi)et i=7;
......進(jìn)入第九次循環(huán),此時其中l(wèi)et i=8;
{//進(jìn)入第十次循環(huán)
? ? let i=9;
? ? a[i]=function(){
? ? ? ? console.log(i);
? ? };//同樣,這個a[i]也是一個閉包
}
{
? ? let i=10;//不符合條件,不再向下執(zhí)行。于是這個代碼塊中不存在閉包,let i=10;在這次循環(huán)結(jié)束之后難逃厄運(yùn),隨即被銷毀。
}
a[6]();//調(diào)用a[6]()函數(shù),這時執(zhí)行環(huán)境隨即進(jìn)入下面這個代碼塊中的執(zhí)行環(huán)境:funcion(){console.log(i)};
{?
? ? ?let i=6;?
? ? ?a[6]=function(){
? ? ? ? ? console.log(i);
? ? ?}; //同樣,這個a[i]也是一個閉包
}
? ? a[6]函數(shù)(閉包)這個執(zhí)行環(huán)境中,它會首先尋找該執(zhí)行環(huán)境中是否存在 i,沒有找到,就沿著作用域鏈繼續(xù)向上到了其所在的代碼塊執(zhí)行環(huán)境,找到了i=6,于是輸出了6,即a[6]();的結(jié)果為6。這時,閉包被調(diào)用,所以整個代碼塊中的變量i和函數(shù)a[6]()被銷毀。
?
相信大家仔細(xì)看完上面的函數(shù)執(zhí)行的過程,對let var 塊級作用域 閉包就有一個很好的理解了。我認(rèn)為重要的是對于函數(shù)執(zhí)行過程的理解!
?
?
二.不存在變量提升
這里是說使用let不會像使用var一樣存在一個變量提升的現(xiàn)象。變量提升是什么呢?在沒有接觸es6之前我對此也不清楚,但是我想大家一定都聽說過函數(shù)聲明提升:函數(shù)聲明來定義函數(shù)即可實現(xiàn)函數(shù)聲明提升,這樣,我們可以先調(diào)用函數(shù),后聲明函數(shù);而函數(shù)表達(dá)式方法不會實現(xiàn)函數(shù)聲明提升,這樣,如果先調(diào)用函數(shù),后聲明函數(shù),則會拋出錯誤!!(對于函數(shù)聲明提升更多知識可以看我的博文《JavaScript函數(shù)之美~》)。? 那么可以以此類推,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;
我們可以看到結(jié)果如下:
即前兩個都是先聲明后使用,沒有問題。而后兩個都是先使用,后聲明,用var 聲明的顯示undefined,而 let聲明的直接報錯。
說明:console.log(i);
var i=10;
實際上相當(dāng)于:
?var i;
?console.log(i);
i=10;
所以會出現(xiàn)undefined的情況。
?
三.暫時性死區(qū)
暫時性死區(qū)即:只要一進(jìn)入當(dāng)前作用域,所要使用的變量就已經(jīng)存在,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量。
例5:
?
????var?tmp=123;????if(true){tmp="abc";let?tmp;}?
? ?結(jié)果如下:
也就是說:雖然上面的代碼中存在全局變量tmp,但是塊級作用域內(nèi)let又聲明了一個局部變量tmp,導(dǎo)致后者綁定了塊級作用域,所以在let聲明變量前,對tmp賦值會報錯。此即暫時性死區(qū)。
注意:ES6規(guī)定暫時性死區(qū)和不存在變量提升就是為了減少運(yùn)行時的錯誤,防止在變量聲明前就使用這個變量,從而導(dǎo)致意料之外的行為。
?
四.不允許重復(fù)聲明
????function?func?(){let?b=100;????????var?b=10;}????function?add(num){let?num;????????return?num+1;}????function?another(){let?a=10;let?a=5;}
上述三個得到的結(jié)果均為:
只是前兩者為 b和num被聲明過了。注意:第二個函數(shù),雖然我們沒有明確的聲明,但是參數(shù)實際上是相當(dāng)于用var聲明的局部變量。
?
?
?
第二部分:const命令
? 什么使const命令呢?實際上它也是一種聲明常量的方式。const命令用來聲明常量,一旦聲明,其值就不能改變。初次之外,const和let十分相似。也就是說前者是用于聲明常量的,后者是用于聲明變量的。
?
1.const聲明常量,一旦聲明,不可改變。
????const?a=10;a=100;結(jié)果如下
2.既然const一旦聲明不可改變,所以在聲明時必須初始化。
const?a;結(jié)果如下:
?
3.const所在的代碼塊為塊級作用域,所以其變量只在塊級作用域內(nèi)使用或其中的閉包使用。
????if(true){const?a=10;}console.log(a);結(jié)果如下:
?
4.const聲明的變量不存在變量提升。
????if(true){console.log(a);const?a=10;}結(jié)果如下:
?
5.const不可重復(fù)聲明常量。
????var?a=10;const?a=5;結(jié)果如下:
?
?
6.const命令只是保證了變量名指向的地址不變,并不保證該地址的數(shù)據(jù)不變。
????const?a={};a.name="zzw";console.log(a.name);?const?b=[];b.push("zzw");console.log(b);const?c={};c={name:"zzw"};
結(jié)果如下:
因此,我們使用const所指向的地址不可變,但是地址的內(nèi)容是可以變得。
轉(zhuǎn)載于:https://blog.51cto.com/zhanglida66/1921426
總結(jié)
以上是生活随笔為你收集整理的ES6之let(理解闭包)和const命令的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用hexo搭建个人博客
- 下一篇: C++ boost thread学习(二