一屎慢、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已聲明還沒(méi)賦值,默認(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)目的過(guò)程中续誉,使用到了ES6,因?yàn)橹昂苌俳佑|初肉,所以使用起來(lái)還不夠熟悉酷鸦。因此購(gòu)買了阮一峰老師的ES6標(biāo)準(zhǔn)入門,在此感謝阮一峰老師的著作牙咏。
我們知道臼隔,ECMAScript 6即ES6是ECMAScript的第五個(gè)版本,因?yàn)樵?015年6月正式發(fā)布妄壶,所以又成為ECMAScript2015摔握。ES6的主要目的是為了是JS用于編寫復(fù)雜的大型應(yīng)用程序,成為企業(yè)級(jí)的開發(fā)語(yǔ)言丁寄。
說(shuō)明:由于有時(shí)候我們希望得知es6代碼的具體實(shí)現(xiàn)原理或者說(shuō)希望能夠轉(zhuǎn)化為es5使用氨淌,我們可以使用http://babeljs.io/來(lái)實(shí)現(xiàn)在線將es6代碼轉(zhuǎn)化為es5代碼。
第一部分:let命令
一.塊級(jí)作用域(重點(diǎn))伊磺。
我們知道盛正,在javascript中只有全局作用域和函數(shù)作用域,并不存在塊級(jí)作用域屑埋。這樣豪筝,在使用時(shí)就會(huì)出現(xiàn)一些問(wèn)題。 下面我們先來(lái)舉例說(shuō)明let塊級(jí)作用域的使用。
例1:
代碼如下所示:
? ? ? ? {
? ? ? ? ? ? vara=5;
? ? ? ? ? ? let b=10;
? ? ? ? }
? ? ? ? console.log(a);
? ? ? ? console.log(b);
我們?cè)诳刂婆_(tái)得到的結(jié)果如下所示:
也就是說(shuō)续崖,var聲明的變量由于不存在塊級(jí)作用域所以可以在全局環(huán)境中調(diào)用敲街,而let聲明的變量由于存在塊級(jí)作用域所以不能在全局環(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來(lái)定義i著蟹,得到的結(jié)果是10.而后者使用的是let來(lái)定義i,最終得到的結(jié)果是6.這是為什么呢梢莽?阮一峰老師在書中的解釋并不是很清楚萧豆,所以下面我會(huì)發(fā)表個(gè)人見解:
關(guān)于這個(gè)問(wèn)題,表面上確實(shí)不是很好理解昏名,查詢了很多資料涮雷,許多人講到了很多晦澀難懂的知識(shí),似乎很高大上轻局,但是實(shí)際上并不難洪鸭,下面根據(jù)我的理解進(jìn)行解釋,如有問(wèn)題仑扑,歡迎批評(píng)指正览爵,如果大家能夠有些收獲就再好不過(guò)了。
例二前者(var i)具體執(zhí)行過(guò)程如下:
var a=[];
var i=0;//由于var來(lái)聲明變量i镇饮,所以for循環(huán)代碼塊不具備塊級(jí)作用域蜓竹,因此i認(rèn)為是全局變量,直接放在全局變量中储藐。
a[0]=function(){
console.log(i);//這里之所以i為i而不是0俱济;是因?yàn)槲覀冎皇嵌x了該函數(shù),未被調(diào)用钙勃,所以沒(méi)有進(jìn)入該函數(shù)執(zhí)行環(huán)境蛛碌,i當(dāng)然不會(huì)沿著作用域鏈向上搜索找到i的值。
}// 由于不具備塊級(jí)作用域辖源,所以該函數(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值裁蚁,所以說(shuō)這時(shí)i為10,那么a[6]的值就是10了继准。
說(shuō)明:對(duì)于例如a[1]=function(){console.log(i)}枉证;而不是a[1]=function{console.log(1)},可以在控制臺(tái)中輸出a[1]函數(shù)移必,即可得到驗(yàn)證室谚。
例二后者(let i)具體執(zhí)行過(guò)程如下:
var a=[];//創(chuàng)建一個(gè)數(shù)組a;
{ //進(jìn)入第一次循環(huán)
let i=0; //注意:因?yàn)槭褂胠et使得for循環(huán)為塊級(jí)作用域,此次let i=0在這個(gè)塊級(jí)作用域中崔泵,而不是在全局環(huán)境中秒赤。
a[0]=function(){
console.log(i);
}; //注意:由于循環(huán)時(shí),let聲明i,所以整個(gè)塊是塊級(jí)作用域憎瘸,那么a[0]這個(gè)函數(shù)就成了一個(gè)閉包入篮。
}//?聲明:?我這里用{}表達(dá)并不符合語(yǔ)法,只是希望通過(guò)它來(lái)說(shuō)明let存在時(shí)幌甘,這個(gè)for循環(huán)塊是塊級(jí)作用域崎弃,而不是全局作用域。
講道理含潘,上面這是一個(gè)塊級(jí)作用域饲做,就像函數(shù)作用域一樣,函數(shù)執(zhí)行完畢遏弱,其中的變量會(huì)被銷毀盆均,但是因?yàn)檫@個(gè)代碼塊中存在一個(gè)閉包,閉包的作用域鏈中包含著(或著說(shuō)是引用著)塊級(jí)作用域漱逸,所以在閉包被調(diào)用之前泪姨,這個(gè)塊級(jí)作用域內(nèi)部的變量不會(huì)被銷毀。(更多閉包知識(shí)饰抒,可以看我的博文《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,沒(méi)有找到贯溅,就沿著作用域鏈繼續(xù)向上到了其所在的代碼塊執(zhí)行環(huán)境拄氯,找到了i=6,于是輸出了6,即a[6]();的結(jié)果為6它浅。這時(shí)译柏,閉包被調(diào)用,所以整個(gè)代碼塊中的變量i和函數(shù)a[6]()被銷毀姐霍。
相信大家仔細(xì)看完上面的函數(shù)執(zhí)行的過(guò)程鄙麦,對(duì)let var 塊級(jí)作用域 閉包就有一個(gè)很好的理解了。我認(rèn)為重要的是對(duì)于函數(shù)執(zhí)行過(guò)程的理解镊折!
二.不存在變量提升
這里是說(shuō)使用let不會(huì)像使用var一樣存在一個(gè)變量提升的現(xiàn)象胯府。變量提升是什么呢?在沒(méi)有接觸es6之前我對(duì)此也不清楚恨胚,但是我想大家一定都聽說(shuō)過(guò)函數(shù)聲明提升:函數(shù)聲明來(lái)定義函數(shù)即可實(shí)現(xiàn)函數(shù)聲明提升骂因,這樣,我們可以先調(diào)用函數(shù)赃泡,后聲明函數(shù)寒波;而函數(shù)表達(dá)式方法不會(huì)實(shí)現(xiàn)函數(shù)聲明提升,這樣升熊,如果先調(diào)用函數(shù)俄烁,后聲明函數(shù),則會(huì)拋出錯(cuò)誤<兑啊R惩馈(對(duì)于函數(shù)聲明提升更多知識(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è)都是先聲明后使用议忽,沒(méi)有問(wèn)題。而后兩個(gè)都是先使用十减,后聲明栈幸,用var 聲明的顯示undefined,而 let聲明的直接報(bào)錯(cuò)帮辟。
說(shuō)明: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é)果如下:
也就是說(shuō):雖然上面的代碼中存在全局變量tmp并炮,但是塊級(jí)作用域內(nèi)let又聲明了一個(gè)局部變量tmp,導(dǎo)致后者綁定了塊級(jí)作用域甥郑,所以在let聲明變量前逃魄,對(duì)tmp賦值會(huì)報(bào)錯(cuò)。此即暫時(shí)性死區(qū)澜搅。
注意:ES6規(guī)定暫時(shí)性死區(qū)和不存在變量提升就是為了減少運(yùn)行時(shí)的錯(cuò)誤伍俘,防止在變量聲明前就使用這個(gè)變量,從而導(dǎo)致意料之外的行為勉躺。
暫時(shí)性死區(qū)就是: 只要塊級(jí)作用域內(nèi)存在let癌瘾,那么他所聲明的變量就綁定了這個(gè)區(qū)域,不再受外部的影響饵溅。
暫時(shí)性死區(qū)即 Temperary Dead Zone柳弄,即TDZ。?
? ? ? ?注意:暫時(shí)性死區(qū)也意味著 typeof 不再是一個(gè)百分之百安全的操作概说。 ?如下:
if(true) {
? ? ? console.log(typeof x);
? ? ? let x;
? ? }
這里如果沒(méi)有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í)際上就是這一部分是有問(wèn)題的 。
四.不允許重復(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被聲明過(guò)了壳影。注意:第二個(gè)函數(shù)拱层,雖然我們沒(méi)有明確的聲明,但是參數(shù)實(shí)際上是相當(dāng)于用var聲明的局部變量宴咧。
第二部分:const命令
什么使const命令呢根灯?實(shí)際上它也是一種聲明常量的方式。const命令用來(lái)聲明常量掺栅,一旦聲明烙肺,其值就不能改變。初次之外氧卧,const和let十分相似桃笙。也就是說(shuō)前者是用于聲明常量的,后者是用于聲明變量的沙绝。
1.const聲明常量搏明,一旦聲明,不可改變闪檬。
const a=10;
? ? a=100;
結(jié)果如下
2.既然const一旦聲明不可改變星著,所以在聲明時(shí)必須初始化。
const a;
結(jié)果如下:
3.const所在的代碼塊為塊級(jí)作用域谬以,所以其變量只在塊級(jí)作用域內(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.如果希望將對(duì)象本身凍結(jié)炕檩,可以使用Object.freeze()方法。
const a=Object.freeze({});
? ? a.name="zzw";
? ? console.log(a.name); //undefined
于是通過(guò)Object.freeze()方法我們就不可以再改變對(duì)象的屬性了(無(wú)效)捌斧。