摘要:聲明的變量存在變量提升,聲明的變量不存在變量提升。聲明的變量允許重新賦值,聲明的變量不允許重新賦值。注意跨腳本聲明重復(fù)變量也會(huì)報(bào)錯(cuò)。中出現(xiàn)的任何元素在聲明中出現(xiàn),語(yǔ)法錯(cuò)誤。中的是如此的怪異。對(duì)中的聲明進(jìn)行實(shí)例化。
我在上一篇文章javascript中詞法環(huán)境、領(lǐng)域、執(zhí)行上下文以及作業(yè)詳解中的最后稍微提到了有關(guān)var、let、const聲明的區(qū)別,在本篇中我會(huì)重點(diǎn)來(lái)分析它們之間到底有什么不同。
提到var、let、const中的區(qū)別很多人一下子就想到了,var聲明的變量是全局或者整個(gè)函數(shù)塊的而let、const聲明的變量是塊級(jí)的變量。var聲明的變量存在變量提升,let、const聲明的變量不存在變量提升。let聲明的變量允許重新賦值,const聲明的變量不允許重新賦值。那么它們之間真的只有這么一點(diǎn)區(qū)別嗎,我們先來(lái)看下面一個(gè)例子:
注:本篇文章中的所有例子都以最新版chrome瀏覽器為標(biāo)準(zhǔn)(低版本瀏覽器實(shí)現(xiàn)會(huì)有區(qū)別)。
//我們看一下這三句話,你認(rèn)為會(huì)發(fā)生什么 let let = 1; console.log(let); // const let = 1; console.log(let); // var let = 1; console.log(let);
很多人會(huì)認(rèn)為,let是關(guān)鍵字,上面這三句聲明都會(huì)報(bào)錯(cuò)。可事實(shí)真的是這樣嗎?不是。let、const的聲明會(huì)報(bào)錯(cuò),但是var聲明被認(rèn)為是規(guī)范的,更重要的是let、const聲明報(bào)錯(cuò)的原因也不是因?yàn)閘et是關(guān)鍵詞而是由于ECMAScript語(yǔ)言規(guī)范中規(guī)定了當(dāng)用let、const聲明時(shí)如果標(biāo)識(shí)符是let則報(bào)錯(cuò)。
該代碼是運(yùn)行在非嚴(yán)格模式下的,嚴(yán)格模式則報(bào)錯(cuò),值得注意的是嚴(yán)格模式下上面三句話都是因?yàn)闃?biāo)識(shí)符let是保留字而報(bào)錯(cuò)的。有興趣可以在嚴(yán)格模式和非嚴(yán)格模式下測(cè)試let let = 1;報(bào)錯(cuò)原因是不同的。
下面的所有代碼都在非嚴(yán)格模式下進(jìn)行,如果是嚴(yán)格模式我會(huì)明確指出。
那么上面三句話中的標(biāo)識(shí)符let改為const會(huì)怎么樣?無(wú)論是嚴(yán)格模式還是非嚴(yán)格模式都報(bào)錯(cuò),錯(cuò)誤原因是因?yàn)閏onst是關(guān)鍵字,這時(shí)候問(wèn)題又來(lái)了,為什么標(biāo)識(shí)符let和const的行為會(huì)不同呢?這個(gè)鍋說(shuō)到底還是得ES5規(guī)范背,在ES5規(guī)范中const被認(rèn)為是未來(lái)保留字(FutureReservedWords)而let只有在嚴(yán)格模式下才被認(rèn)為是未來(lái)保留字,這導(dǎo)致var可以聲明let卻不能聲明const,那到了ES6時(shí)代為什么不改呢?哎!不是不改而是心有力而余不足啊,鬼知道在ES6時(shí)代之前有多少代碼中出現(xiàn)過(guò)var let這個(gè)聲明啊,這要是改了得有多少網(wǎng)站得炸啊。
基于上面的原因,你看到下面的代碼時(shí)不要驚訝:
var let = 1; console.log(let); //1 let a = 2; console.log(a); //2 //看著怪異但是完全可以工作,不會(huì)有任何錯(cuò)誤
看完上面一個(gè)不同點(diǎn),我們?cè)倏聪旅孢@個(gè)例子:
var a; console.log(a); //undefined // let a; console.log(a); //undefined // const a; console.log(a); //?
我們都知道如果var和let只聲明變量而不賦值,那么默認(rèn)賦值undefined,那么const會(huì)怎樣呢?
你在Chrome控制臺(tái)上試一下就知道了,語(yǔ)法錯(cuò)誤缺少初始化,ES6規(guī)范指出const聲明的標(biāo)識(shí)符一定要初始化賦值,這不是運(yùn)行時(shí)錯(cuò)誤,這是個(gè)早期錯(cuò)誤,編譯器在執(zhí)行腳本之前會(huì)檢測(cè)早期錯(cuò)誤。
我們接著看下一個(gè)問(wèn)題:
let a = 1; let a = 2;
var可以重復(fù)聲明變量,那么let和const可以嗎?答案是不可以。你可以認(rèn)為let和const聲明的變量名稱在該作用域內(nèi)是唯一的,不能重復(fù)聲明。那如果用var可以覆蓋let聲明的變量嗎?答案是不能。不管你是let或const先聲明變量var后面重復(fù)聲明,還是var先聲明變量let或const后聲明都會(huì)報(bào)錯(cuò)。這個(gè)錯(cuò)誤是一個(gè)早期錯(cuò)誤。
塊(Block)注意:let/const跨腳本聲明重復(fù)變量也會(huì)報(bào)錯(cuò)。但這個(gè)時(shí)候的錯(cuò)誤被認(rèn)為是運(yùn)行時(shí)錯(cuò)誤,不是早期錯(cuò)誤。上面所指的let/const聲明都指在同一作用域下。
上面列出了var、let、const靜態(tài)語(yǔ)義上的區(qū)別。在該小節(jié)中我會(huì)講述在javascript內(nèi)部它們之間的不同,不過(guò)在此我們先要了解(塊)Block,可以說(shuō)let、const是因?yàn)锽lock存在的。
不過(guò)提到Block之前我們需要花幾分鐘了解幾個(gè)名詞:
我拿個(gè)例子簡(jiǎn)單說(shuō)明一下:
//全局聲明 var a=1; let b=1; const c=1; function foo(){}; class Foo{}; { //塊級(jí)聲明 var ba=1; let bb=1; const bc=1; class BFoo{}; function bfoo(){} }
LexicallyDeclaredNames(詞法聲明名稱列表):? bb,bc,bfoo,BFoo ?
LexicallyScopedDeclarations(詞法作用域聲明列表):? let bb=1,const bc=1,function bfoo(){},class BFoo{} ?
VarDeclaredNames(var聲明名稱列表):? ba ?
VarScopedDeclarations(var作用域聲明列表):? ba=1 ?
TopLevelLexicallyDeclaredNames(頂級(jí)詞法聲明名稱列表):? b,c,Foo ?
TopLevelLexicallyScopedDeclarations(頂級(jí)詞法作用域聲明列表):? let b=1,const c=1,class Foo{} ?
TopLevelVarDeclaredNames(頂級(jí)var聲明名稱列表):? a,ba,bfoo ?
TopLevelVarScopedDeclarations(頂級(jí)var作用域聲明列表):? a=1,ba=1,function foo(){}?
注:? ?結(jié)構(gòu)是ECMAScript中的一個(gè)規(guī)范類型,表示一個(gè)List,具體你可以認(rèn)為它是一個(gè)類數(shù)組(當(dāng)然實(shí)際肯定不是,只是方便理解)
有沒(méi)有看到怪異的地方?function聲明在頂級(jí)作用域(TopLevel)中被視為var聲明,而不在頂級(jí)作用域也就是Block或catch塊中被認(rèn)為是詞法聲明,這就導(dǎo)致了一些有趣的事情。
Block只有前四個(gè)列表,函數(shù)(function)和腳本(script)只有后四個(gè)列表(其實(shí)函數(shù)和腳本也只有前四個(gè),不過(guò)前四個(gè)列表的值取的是后四個(gè)列表的值)。Block雖然有自己的作用域但是它和函數(shù)有著本質(zhì)上的區(qū)別。函數(shù)和腳本你可以看成是相互獨(dú)立的而B(niǎo)lock是屬于function和script的一部分。具體就是Block中的var聲明同時(shí)也被認(rèn)為是頂級(jí)聲明,不管你嵌了多少層塊在里面都不會(huì)變,因?yàn)锽lock沒(méi)有頂級(jí)作用域。
理解了上面的8個(gè)名稱,我們?cè)賮?lái)看看Block中的聲明與function和script中有何不同:
LexicallyDeclaredNames中如果包含任何重復(fù)項(xiàng),則語(yǔ)法錯(cuò)誤。
LexicallyDeclaredNames中出現(xiàn)的任何元素在VarDeclaredNames聲明中出現(xiàn),語(yǔ)法錯(cuò)誤。
規(guī)則1很正常,LexicallyDeclaredNames這個(gè)列表里不能有重復(fù)項(xiàng),即不能重復(fù)聲明。
規(guī)則2這就很有意思了,我們上面說(shuō)到了在Block中function聲明屬于詞法聲明,于是你會(huì)在Block中看到:
{ var foo=1; function foo(){} //Syntax Error,var和function不能聲明同一個(gè)標(biāo)識(shí)符,腳本和函數(shù)中是不存在這個(gè)問(wèn)題的。 //我大膽推測(cè)一下,可能在不久的將來(lái)腳本和函數(shù)中var和function也不能聲明同一個(gè)標(biāo)識(shí)符了。 }
補(bǔ)充規(guī)則1中function聲明
{ function a(){}; function a(){}; //it"s ok,no syntax Error } //----------------------- "use strict"; { function a(){}; function a(){}; //error, syntax Error redeclaration a; }
這里我不得不吐槽一下了,就因?yàn)樵诜菄?yán)格模式下Block中的function可以重復(fù)聲明害我以為規(guī)范1我理解錯(cuò)了,導(dǎo)致我把文檔中有關(guān)Block規(guī)范說(shuō)明部分翻來(lái)覆去看了好幾遍,最后我才在規(guī)范文檔的附錄中找到原因:為了實(shí)現(xiàn)網(wǎng)頁(yè)瀏覽器的兼容性,允許在非嚴(yán)格模式下的Block中的function可以重復(fù)聲明。
這里有個(gè)建議,最好永遠(yuǎn)不要在一個(gè)作用域內(nèi)同時(shí)使用var和let/const聲明,還有不要在Block中使用var聲明,至于Block中的function聲明,除非你確切的知道你需要這個(gè)function做什么,否則也不要在Block中使用function。Block中的function是如此的怪異。
1.非嚴(yán)格模式下,block中的function聲明的標(biāo)識(shí)符會(huì)被提到頂級(jí)作用域下,但是只提標(biāo)識(shí)符,并賦值undefined,不提函數(shù)體。你可以把它看成是一個(gè)var聲明的變量,具體如下:
console.log(foo); //undefined { function foo(){ console.log(1); } } foo(); //1
2.非嚴(yán)格模式下,block中的function聲明的函數(shù)對(duì)象對(duì)這個(gè)block來(lái)說(shuō)形成了一個(gè)閉包,我認(rèn)為‘閉包’這個(gè)詞是最好的解釋:
var a = "outer a"; { let a = "inner a"; function foo(){ console.log(a); } } console.log(a) //outer a foo(); //inner a, not outer a
3.嚴(yán)格模式下,block中的function聲明只能在block中訪問(wèn)到,離開(kāi)這個(gè)block無(wú)法訪問(wèn):
"use strict"; console.log(foo); //Uncaught ReferenceError: foo is not defined { function foo(){ console.log(1); } } foo(); //Uncaught ReferenceError: foo is not defined
出現(xiàn)這種情況是因?yàn)镋S5之前,block中不能出現(xiàn)function聲明,但是不同的瀏覽器實(shí)現(xiàn)不一樣,到了現(xiàn)在只能通過(guò)瀏覽器擴(kuò)展進(jìn)行填補(bǔ)。在非嚴(yán)格模式下,編譯器進(jìn)行全局聲明實(shí)例化是也就是上篇文章中說(shuō)道的GlobalDeclarationInstantiation方法時(shí)會(huì)對(duì)block、switch中case和default語(yǔ)句中的function聲明進(jìn)行額外的操作,如果function聲明的標(biāo)識(shí)符在全局環(huán)境下沒(méi)有找打其它的詞法聲明名稱即在TopLevelLexicallyDeclaredNames列表中不存在function聲明的標(biāo)識(shí)符,則在全局環(huán)境記錄下創(chuàng)建function綁定,但是設(shè)置的值不是聲明的函數(shù)體而是是undefined。函數(shù)中有相似的操作。
block中的一些注意點(diǎn)以及和function還有script中的區(qū)別我大致講了一下。那么block是如何做到有塊級(jí)作用域的功能的呢?
我在上一篇文章中講到了執(zhí)行上下文,提到執(zhí)行上下文是編譯器用來(lái)跟蹤代碼執(zhí)行時(shí)評(píng)估的一種規(guī)范設(shè)備,每個(gè)執(zhí)行上下文都有自己的LexicalEnvironment和VariableEnvironment組件。編譯器在評(píng)估Block做了如下操作:
讓oldEnv成為正在運(yùn)行的執(zhí)行上下文(running execution context)的LexicalEnvironment。
讓blockEnv成為一個(gè)新的聲明性環(huán)境,它的外部詞法環(huán)境引用指向oldEnv。
對(duì)block中的聲明進(jìn)行實(shí)例化。
把正在運(yùn)行的執(zhí)行上下文(running execution context)的LexicalEnvironment設(shè)為blockEnv。
讓blockValue成為執(zhí)行block中的代碼的結(jié)果。
把正在運(yùn)行的執(zhí)行上下文(running execution context)的LexicalEnvironment設(shè)為oldEnv。
返回blockValue。
我們看到了執(zhí)行block中代碼時(shí)不會(huì)新建執(zhí)行上下文,它只是改變了正在運(yùn)行的執(zhí)行上下文的LexicalEnvironment組件值,block運(yùn)行完成后又恢復(fù)成以前的LexicalEnvironment組件,這指明了block中聲明的變量只在該block中起作用,這也表示為什么block是塊級(jí)作用域。這跟函數(shù)不一樣,執(zhí)行函數(shù)時(shí)會(huì)創(chuàng)建新的執(zhí)行上下文。
我這再說(shuō)明一下,步驟3中的聲明進(jìn)行實(shí)例化指得是LexicallyScopedDeclarations列表中的聲明,block不會(huì)對(duì)其中的var聲明進(jìn)行操作。步驟5中的blockValue指得是block中最后一個(gè)語(yǔ)句執(zhí)行后的返回值。
知道了這個(gè),我們來(lái)看個(gè)let和var在Block中的不同:
for(var i = 0;i < 10;i++){ setTimeout(function(){console.log(i)}) } //輸出10個(gè)10 for(let i=0;i<10;i++){ setTimeout(function(){console.log(i)}) } //輸出0到9
我這邊做個(gè)簡(jiǎn)單說(shuō)明:
把全局環(huán)境記錄記gec,for循環(huán)里的環(huán)境記錄記為bec,匿名函數(shù)的環(huán)境記錄記為fec。
gec的外部環(huán)境null,bec的外部環(huán)境gec,fec的外部環(huán)境bec。
第一個(gè)for循環(huán)中函數(shù)輸出i,fec中沒(méi)有i的記錄,向外找bec,沒(méi)有i的記錄,向外找找gec,發(fā)現(xiàn)i,值為10,所以輸出10個(gè)10。
第二個(gè)for循環(huán)中函數(shù)輸出i,fec中沒(méi)有i的記錄,向外找bec,找到i的記錄,并輸出i,這個(gè)i是當(dāng)前bec記錄中i的值,每次循環(huán)都會(huì)創(chuàng)建一個(gè)新的bec記錄。
變量提升(Hoisting)我們都知道var和function聲明在作用域內(nèi)存在著變量提升,但是let/const或者class呢?究竟有沒(méi)有存在變量提升。這個(gè)問(wèn)題存在著爭(zhēng)議,可謂仁者見(jiàn)仁智者見(jiàn)智。
我在上篇文章中提到了全局聲明實(shí)例化和block中的block聲明實(shí)例化以及沒(méi)有提到的function聲明實(shí)例化,你會(huì)發(fā)現(xiàn)一個(gè)關(guān)鍵,就是這些操作都是在執(zhí)行代碼之前做的,全局聲明實(shí)例化在腳本執(zhí)行之前進(jìn)行,block聲明實(shí)例化在block中的代碼執(zhí)行之前進(jìn)行,包括函數(shù)也是如此。那么聲明實(shí)例化究竟是做什么的呢?
具體的操作就是把存在LexicallyScopedDeclarations、VarScopedDeclarations、TopLevelLexicallyScopedDeclarations和TopLevelVarScopedDeclarations的信息進(jìn)行操作,存到環(huán)境記錄中。這些詞都是靜態(tài)語(yǔ)義,也就在在腳本執(zhí)行之前就已經(jīng)存儲(chǔ)了。
var a = 1; let b = 1; //執(zhí)行代碼前環(huán)境記錄(Environment Record)綁定了a,b,并給a賦值為undefined,b不賦值。 //注:let、const和class只綁定(實(shí)例化)不初始化,var和function會(huì)進(jìn)行初始化,function初始化指的就是整個(gè)函數(shù)。 //執(zhí)行代碼時(shí)---------------- console.log(a); //undefined 環(huán)境記錄中有a的這個(gè)綁定,并且值是undefined,所以輸出undefined var a = 1; //---------------- console.log(a); //Uncaught ReferenceError: a is not defined 環(huán)境記錄中有a的這個(gè)綁定,但是沒(méi)有值,所以error。 //可能a is not defined改為a is not initialized更能讓人容易理解。 // not defined容易和undefined混淆。 let a = 1; //一個(gè)更好的例子 var a = 1; { console.log(a); //Uncaught ReferenceError: a is not defined,not value 1; let a = 2; //let聲明的變量實(shí)際上也提升了 }
正是這樣原因?qū)е隆白兞刻嵘贝嬖跔?zhēng)議,一部分人認(rèn)為let、const、class和var一樣,在一開(kāi)始就已經(jīng)提升了,所以let、const、class存在“變量提升”。有的人認(rèn)為所謂“變量提升”,是指代碼不報(bào)錯(cuò),還能運(yùn)行,而let、const、class會(huì)出現(xiàn)錯(cuò)誤,所以不能算“變量提升”。
ECMAScript規(guī)范一直沒(méi)有給出準(zhǔn)確的說(shuō)明,甚至不同版本說(shuō)法不一樣,在最新的ES8規(guī)范中雖然沒(méi)有給出準(zhǔn)確的說(shuō)明,但是規(guī)范定義了一個(gè)HoistableDeclaration文法,該文法中包含了FunctionDeclaration、GeneratorDeclaration和AsyncFunctionDeclaration文法。HoistableDeclaration文法又與ClassDeclaration和LexicalDeclaration(let/const的語(yǔ)法規(guī)則)文法組成Declaration文法。
這里是不是可以推斷出ECMAScript規(guī)范認(rèn)為let、const和class不存在“變量提升”呢。當(dāng)然這只是我的一個(gè)推測(cè)。
結(jié)束語(yǔ)到這里let/const和var的解釋基本就完結(jié)了。我大致的對(duì)let/const以及var做了一個(gè)區(qū)別介紹,但是還有很多小的細(xì)節(jié)不能涵蓋到,如果感興趣想了解更多的話可以查看官方文檔13.2 Block和13.3 let/const和var。
算上最開(kāi)始的javascript強(qiáng)制轉(zhuǎn)化,這是我對(duì)ES8文檔講解的第三篇文章,之后我會(huì)陸續(xù)發(fā)表一些我對(duì)ES8文檔的理解,希望能與人一起交流共進(jìn)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/90105.html
摘要:前言和的區(qū)別是老生常談,看到網(wǎng)上一些文章的總結(jié),有的不太全面,甚至有的描述不太準(zhǔn)確,在這里盡量全面的總結(jié)下這三者的區(qū)別。最后以上大概是總結(jié)后的內(nèi)容,看來(lái),還是多用吧。 前言 var 和 let 的區(qū)別是老生常談,看到網(wǎng)上一些文章的總結(jié),有的不太全面,甚至有的描述不太準(zhǔn)確,在這里盡量全面的總結(jié)下這三者的區(qū)別。 let 是 ES6新增的變量類型,用來(lái)代替 var 的一些缺陷,跟 var...
摘要:在的閉包中,閉包函數(shù)能夠訪問(wèn)到包庇函數(shù)中的變量,這些閉包函數(shù)能夠訪問(wèn)到的變量也因此被稱為自由變量。在之前最常見(jiàn)的兩種作用域,全局作用局和函數(shù)作用域局部作用域。 關(guān)于文章討論請(qǐng)?jiān)L問(wèn):https://github.com/Jocs/jocs.... 當(dāng)Brendan Eich在1995年設(shè)計(jì)JavaScript第一個(gè)版本的時(shí)候,考慮的不是很周到,以至于最初版本的JavaScript有很多不...
摘要:會(huì)出現(xiàn)這樣的情況是因?yàn)閾碛袝簳r(shí)性死區(qū)。規(guī)定暫時(shí)性死區(qū)和語(yǔ)句不出現(xiàn)變量提升,主要是為了減少運(yùn)行時(shí)錯(cuò)誤,防止在變量聲明前就使用這個(gè)變量,從而導(dǎo)致意料之外的行為。 首先我們應(yīng)該知道js引擎在讀取js代碼時(shí)會(huì)進(jìn)行兩個(gè)步驟: 第一個(gè)步驟是解釋。 第二個(gè)步驟是執(zhí)行。 所謂解釋就是會(huì)先通篇掃描所有的Js代碼,然后把所有聲明提升到頂端,第二步是執(zhí)行,執(zhí)行就是操作一類的。 我們先來(lái)看個(gè)簡(jiǎn)單的變量提升...
摘要:概述發(fā)布前,只能通過(guò)聲明變量的方式,常量塊級(jí)變量函數(shù)變量這些概念的差別都不能很好的體現(xiàn)出來(lái),于此同時(shí),加入你要使用或者提供一個(gè),聲明的變量可隨時(shí)被修改和重新分配的問(wèn)題,會(huì)讓你時(shí)刻擔(dān)心代碼是否能正常運(yùn)行。 1. var、let、const概述 ES6發(fā)布前,Javascript只能通過(guò)var聲明變量的方式,常量、塊級(jí)變量、函數(shù)變量這些概念的差別都不能很好的體現(xiàn)出來(lái),于此同時(shí),加入你要使用...
摘要:變量提升是在預(yù)編譯的過(guò)程中發(fā)生的,賦值為被聲明的變量還是在原來(lái)的地方,真正被賦值塊級(jí)聲明塊級(jí)聲明用于聲明在指定塊的作用域之外無(wú)法訪問(wèn)的變量。只有執(zhí)行變量聲明語(yǔ)句后,變量才會(huì)從中移出,然后才可以正常訪問(wèn)。 在代碼中,聲明變量是基礎(chǔ),但是在javascript中,經(jīng)歷了從var到let,const的變化,到底有什么本質(zhì)上的區(qū)別呢? 本文的原文在我的博客中:https://github.co...
閱讀 2217·2021-11-22 13:54
閱讀 3380·2019-08-29 12:25
閱讀 3444·2019-08-28 18:29
閱讀 3589·2019-08-26 13:40
閱讀 3278·2019-08-26 13:32
閱讀 962·2019-08-26 11:44
閱讀 2235·2019-08-23 17:04
閱讀 2973·2019-08-23 17:02