一、var聲明的變量會(huì)掛載在window上,而let和const聲明的變量不會(huì)
var a =100;
console.log(a,window.a); // 100 100
let b =10;
console.log(b,window.b);// 10 undefined
const c =1;
console.log(c,window.c);// 1 undefined
二、var聲明變量存在變量提升,let和const不存在變量提升
console.log(a);// undefined? ===>? a已聲明還沒賦值,默認(rèn)得到undefined值
var a =100;
console.log(b);// 報(bào)錯(cuò):b is not defined? ===> 找不到b這個(gè)變量
let b =10;
console.log(c);// 報(bào)錯(cuò):c is not defined? ===> 找不到c這個(gè)變量
const c =10;
三、let和const聲明形成塊作用域,而var不存在此作用域
if(true){
var a =100;
letb =10;
const c =1;
}
console.log(a);// 100
console.log(b)// 報(bào)錯(cuò):b is not defined? ===> 找不到b這個(gè)變量
console.log(c)// 報(bào)錯(cuò):c is not defined? ===> 找不到c這個(gè)變量
注:var聲明的變量屬于全局變量,可以修改,可以聲明相同名字的變量,let、const不可以,只能在作用域內(nèi)使用
ES6之let(理解閉包)和const命令
最近做項(xiàng)目的過程中,使用到了ES6,因?yàn)橹昂苌俳佑|,所以使用起來還不夠熟悉。因此購買了阮一峰老師的ES6標(biāo)準(zhǔn)入門,在此感謝阮一峰老師的著作。
我們知道,ECMAScript 6即ES6是ECMAScript的第五個(gè)版本,因?yàn)樵?015年6月正式發(fā)布,所以又成為ECMAScript2015。ES6的主要目的是為了是JS用于編寫復(fù)雜的大型應(yīng)用程序,成為企業(yè)級的開發(fā)語言。
說明:由于有時(shí)候我們希望得知es6代碼的具體實(shí)現(xiàn)原理或者說希望能夠轉(zhuǎn)化為es5使用,我們可以使用http://babeljs.io/來實(shí)現(xiàn)在線將es6代碼轉(zhuǎn)化為es5代碼。
第一部分:let命令
一.塊級作用域(重點(diǎn))。
我們知道,在javascript中只有全局作用域和函數(shù)作用域,并不存在塊級作用域。這樣,在使用時(shí)就會(huì)出現(xiàn)一些問題。 下面我們先來舉例說明let塊級作用域的使用。
例1:
代碼如下所示:
? ? ? ? {
? ? ? ? ? ? vara=5;
? ? ? ? ? ? let b=10;
? ? ? ? }
? ? ? ? console.log(a);
? ? ? ? console.log(b);
我們在控制臺(tái)得到的結(jié)果如下所示:
也就是說,var聲明的變量由于不存在塊級作用域所以可以在全局環(huán)境中調(diào)用,而let聲明的變量由于存在塊級作用域所以不能在全局環(huán)境中調(diào)用。
例2:這個(gè)例子是一個(gè)非常經(jīng)典的例子。
vara=[];
? ? ? ? for(vari=0;i<10;i++){
? ? ? ? ? ? a[i]=function(){
? ? ? ? ? ? ? ? console.log(i);
? ? ? ? ? ? };
? ? ? ? }
? ? ? ? a[6]();//10? ?
vara=[];
? ? for(let i=0;i<10;i++){
? ? ? ? a[i]=function(){
? ? ? ? ? ? console.log(i);
? ? ? ? };
? ? }
? ? a[6]();//6? ?
我們可以看到,兩個(gè)例子中,唯一的區(qū)別是前者for循環(huán)中使用var來定義i,得到的結(jié)果是10.而后者使用的是let來定義i,最終得到的結(jié)果是6.這是為什么呢?阮一峰老師在書中的解釋并不是很清楚,所以下面我會(huì)發(fā)表個(gè)人見解:
關(guān)于這個(gè)問題,表面上確實(shí)不是很好理解,查詢了很多資料,許多人講到了很多晦澀難懂的知識,似乎很高大上,但是實(shí)際上并不難,下面根據(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;是因?yàn)槲覀冎皇嵌x了該函數(shù),未被調(diào)用,所以沒有進(jìn)入該函數(shù)執(zhí)行環(huán)境,i當(dāng)然不會(huì)沿著作用域鏈向上搜索找到i的值。
}// 由于不具備塊級作用域,所以該函數(shù)定義就是全局作用域。
var i=1;//第二次循環(huán),這時(shí)var i=1;覆蓋了前面的var i=0;即現(xiàn)在i為1;
a[1]=function(){
console.log(i);//解釋同a[0]函數(shù)。
}
var i=2;// 第三次循環(huán),這時(shí) i=2,在全局作用域中,所以覆蓋了前面的i=1;
a[2]=function(){
console.log(i);
}
......第四次循環(huán) 此時(shí)i=3這個(gè)以及下面的i不斷的覆蓋前面的i,因?yàn)槎荚谌肿饔糜蛑?/p>
......第五次循環(huán) 此時(shí)i=4
......第六次循環(huán) 此時(shí)i=5
......第七次循環(huán) 此時(shí)i=6
......第八次循環(huán) 此時(shí)i=7
......第九次循環(huán) 此時(shí)i=8
var i=9;
a[9]=function(){
console.log(i);
}
var i=10;// 這時(shí)i為10,因?yàn)椴粷M足循環(huán)條件,所以停止循環(huán)。
緊接著在全局環(huán)境中繼續(xù)向下執(zhí)行。
a[6]();//這時(shí)調(diào)用a[6]函數(shù),所以這時(shí)隨即進(jìn)入a[6]函數(shù)的執(zhí)行環(huán)境,即a[6]=function(){console.log(i)};執(zhí)行函數(shù)中的代碼 console.log(i); 因?yàn)樵诤瘮?shù)執(zhí)行環(huán)境中不存在變量i,所以此時(shí)會(huì)沿著作用域鏈向上尋找(可參考我的博文《深入理解作用域和作用域鏈》),即進(jìn)入了全局作用域中尋找變量i,而全局作用域中i=10覆蓋了前面所有的i值,所以說這時(shí)i為10,那么a[6]的值就是10了。
說明:對于例如a[1]=function(){console.log(i)};而不是a[1]=function{console.log(1)},可以在控制臺(tái)中輸出a[1]函數(shù),即可得到驗(yàn)證。
例二后者(let i)具體執(zhí)行過程如下:
var a=[];//創(chuàng)建一個(gè)數(shù)組a;
{ //進(jìn)入第一次循環(huán)
let i=0; //注意:因?yàn)槭褂胠et使得for循環(huán)為塊級作用域,此次let i=0在這個(gè)塊級作用域中,而不是在全局環(huán)境中。
a[0]=function(){
console.log(i);
}; //注意:由于循環(huán)時(shí),let聲明i,所以整個(gè)塊是塊級作用域,那么a[0]這個(gè)函數(shù)就成了一個(gè)閉包。
}//?聲明:?我這里用{}表達(dá)并不符合語法,只是希望通過它來說明let存在時(shí),這個(gè)for循環(huán)塊是塊級作用域,而不是全局作用域。
講道理,上面這是一個(gè)塊級作用域,就像函數(shù)作用域一樣,函數(shù)執(zhí)行完畢,其中的變量會(huì)被銷毀,但是因?yàn)檫@個(gè)代碼塊中存在一個(gè)閉包,閉包的作用域鏈中包含著(或著說是引用著)塊級作用域,所以在閉包被調(diào)用之前,這個(gè)塊級作用域內(nèi)部的變量不會(huì)被銷毀。(更多閉包知識,可以看我的博文《JavaScript之閉包》)
{ //進(jìn)入第二次循環(huán)
let i=1; //注意:因?yàn)閘et i=1; 和?上面的let i=0;出在不同的作用域中,所以兩者不會(huì)相互影響。
a[1]=function(){
console.log(i);
}; //同樣,這個(gè)a[i]也是一個(gè)閉包
}
......進(jìn)入第三次循環(huán),此時(shí)其中l(wèi)et i=2;
......進(jìn)入第四次循環(huán),此時(shí)其中l(wèi)et i=3;
......進(jìn)入第五次循環(huán),此時(shí)其中l(wèi)et i=4;
......進(jìn)入第六次循環(huán),此時(shí)其中l(wèi)et i=5;
......進(jìn)入第七次循環(huán),此時(shí)其中l(wèi)et i=6;
......進(jìn)入第八次循環(huán),此時(shí)其中l(wèi)et i=7;
......進(jìn)入第九次循環(huán),此時(shí)其中l(wèi)et i=8;
{//進(jìn)入第十次循環(huán)
let i=9;
a[i]=function(){
console.log(i);
};//同樣,這個(gè)a[i]也是一個(gè)閉包
}
{
let i=10;//不符合條件,不再向下執(zhí)行。于是這個(gè)代碼塊中不存在閉包,let i=10;在這次循環(huán)結(jié)束之后難逃厄運(yùn),隨即被銷毀。
}
a[6]();//調(diào)用a[6]()函數(shù),這時(shí)執(zhí)行環(huán)境隨即進(jìn)入下面這個(gè)代碼塊中的執(zhí)行環(huán)境:funcion(){console.log(i)};
{?
let i=6;?
a[6]=function(){
console.log(i);
}; //同樣,這個(gè)a[i]也是一個(gè)閉包
}
? ? a[6]函數(shù)(閉包)這個(gè)執(zhí)行環(huán)境中,它會(huì)首先尋找該執(zhí)行環(huán)境中是否存在 i,沒有找到,就沿著作用域鏈繼續(xù)向上到了其所在的代碼塊執(zhí)行環(huán)境,找到了i=6,于是輸出了6,即a[6]();的結(jié)果為6。這時(shí),閉包被調(diào)用,所以整個(gè)代碼塊中的變量i和函數(shù)a[6]()被銷毀。
相信大家仔細(xì)看完上面的函數(shù)執(zhí)行的過程,對let var 塊級作用域 閉包就有一個(gè)很好的理解了。我認(rèn)為重要的是對于函數(shù)執(zhí)行過程的理解!
二.不存在變量提升
這里是說使用let不會(huì)像使用var一樣存在一個(gè)變量提升的現(xiàn)象。變量提升是什么呢?在沒有接觸es6之前我對此也不清楚,但是我想大家一定都聽說過函數(shù)聲明提升:函數(shù)聲明來定義函數(shù)即可實(shí)現(xiàn)函數(shù)聲明提升,這樣,我們可以先調(diào)用函數(shù),后聲明函數(shù);而函數(shù)表達(dá)式方法不會(huì)實(shí)現(xiàn)函數(shù)聲明提升,這樣,如果先調(diào)用函數(shù),后聲明函數(shù),則會(huì)拋出錯(cuò)誤!?。?/b>對于函數(shù)聲明提升更多知識可以看我的博文《JavaScript函數(shù)之美~》)。那么可以以此類推,var定義變量:可以先使用,后聲明;而let定義變量:只可先聲明,后使用。
例3:
varnum1=100;
? ? ? ? console.log(num1);
? ? ? ? let num2=200;
? ? ? ? console.log(num2);
? ? ? ? console.log(i);
? ? ? ? vari=10;
? ? ? ? console.log(j);
? ? ? ? let j=5;
我們可以看到結(jié)果如下:
即前兩個(gè)都是先聲明后使用,沒有問題。而后兩個(gè)都是先使用,后聲明,用var 聲明的顯示undefined,而 let聲明的直接報(bào)錯(cuò)。
說明:console.log(i);
var i=10;
實(shí)際上相當(dāng)于:
?var i;
?console.log(i);
i=10;
所以會(huì)出現(xiàn)undefined的情況。
三.暫時(shí)性死區(qū)
暫時(shí)性死區(qū)即:只要一進(jìn)入當(dāng)前作用域,所要使用的變量就已經(jīng)存在,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量。
例5:
vartmp=123;
? ? if(true){
? ? ? ? tmp="abc";
? ? ? ? let tmp;
? ? }
? ?結(jié)果如下:
也就是說:雖然上面的代碼中存在全局變量tmp,但是塊級作用域內(nèi)let又聲明了一個(gè)局部變量tmp,導(dǎo)致后者綁定了塊級作用域,所以在let聲明變量前,對tmp賦值會(huì)報(bào)錯(cuò)。此即暫時(shí)性死區(qū)。
注意:ES6規(guī)定暫時(shí)性死區(qū)和不存在變量提升就是為了減少運(yùn)行時(shí)的錯(cuò)誤,防止在變量聲明前就使用這個(gè)變量,從而導(dǎo)致意料之外的行為。
暫時(shí)性死區(qū)就是: 只要塊級作用域內(nèi)存在let,那么他所聲明的變量就綁定了這個(gè)區(qū)域,不再受外部的影響。
暫時(shí)性死區(qū)即 Temperary Dead Zone,即TDZ。?
? ? ? ?注意:暫時(shí)性死區(qū)也意味著 typeof 不再是一個(gè)百分之百安全的操作。 ?如下:
if(true) {
? ? ? console.log(typeof x);
? ? ? let x;
? ? }
這里如果沒有l(wèi)et x,那么typeof x的結(jié)果是 undefined,但是如果使用了let x,因?yàn)閘et不存在變量提升,所以這里形成了暫時(shí)性死區(qū),即typeof x也是會(huì)報(bào)錯(cuò)的。。。 ?從這里可以理解暫時(shí)性死區(qū)實(shí)際上就是這一部分是有問題的 。
四.不允許重復(fù)聲明
function func (){
? ? ? ? let b=100;
? ? ? ? varb=10;
? ? }
? ? function add(num){
? ? ? ? let num;
? ? ? ? returnnum+1;
? ? }
? ? function another(){
? ? ? ? let a=10;
? ? ? ? let a=5;
? ? }
上述三個(gè)得到的結(jié)果均為:
只是前兩者為 b和num被聲明過了。注意:第二個(gè)函數(shù),雖然我們沒有明確的聲明,但是參數(shù)實(shí)際上是相當(dāng)于用var聲明的局部變量。
第二部分:const命令
什么使const命令呢?實(shí)際上它也是一種聲明常量的方式。const命令用來聲明常量,一旦聲明,其值就不能改變。初次之外,const和let十分相似。也就是說前者是用于聲明常量的,后者是用于聲明變量的。
1.const聲明常量,一旦聲明,不可改變。
const a=10;
? ? a=100;
結(jié)果如下
2.既然const一旦聲明不可改變,所以在聲明時(shí)必須初始化。
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ù)聲明常量。
vara=10;
? ? const a=5;
結(jié)果如下:
need-to-insert-img
6.const命令只是保證了變量名指向的地址不變,并不保證該地址的數(shù)據(jù)不變。
need-to-insert-img
const a={};
? ? a.name="zzw";
? ? console.log(a.name);
? ? const b=[];
? ? b.push("zzw");
? ? console.log(b);
? ? const c={};
? ? c={name:"zzw"};
need-to-insert-img
結(jié)果如下:
need-to-insert-img
因此,我們使用const所指向的地址不可變,但是地址的內(nèi)容是可以變得。
7.如果希望將對象本身凍結(jié),可以使用Object.freeze()方法。
const a=Object.freeze({});
? ? a.name="zzw";
? ? console.log(a.name); //undefined
于是通過Object.freeze()方法我們就不可以再改變對象的屬性了(無效)。