免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
(12) javascript中var、let、const聲明的區(qū)別

我在上一篇文章javascript中詞法環(huán)境、領域、執(zhí)行上下文以及作業(yè)詳解中的最后稍微提到了有關var、let、const聲明的區(qū)別,在本篇中我會重點來分析它們之間到底有什么不同。

提到var、let、const中的區(qū)別很多人一下子就想到了,var聲明的變量是全局或者整個函數(shù)塊的而let、const聲明的變量是塊級的變量。var聲明的變量存在變量提升,let、const聲明的變量不存在變量提升。let聲明的變量允許重新賦值,const聲明的變量不允許重新賦值。那么它們之間真的只有這么一點區(qū)別嗎,我們先來看下面一個例子:

注:本篇文章中的所有例子都以最新版chrome瀏覽器為標準(低版本瀏覽器實現(xiàn)會有區(qū)別)。

//我們看一下這三句話,你認為會發(fā)生什么let let = 1;console.log(let);//const let = 1;console.log(let);//var let = 1;console.log(let);

很多人會認為,let是關鍵字,上面這三句聲明都會報錯。可事實真的是這樣嗎?不是。let、const的聲明會報錯,但是var聲明被認為是規(guī)范的,更重要的是let、const聲明報錯的原因也不是因為let是關鍵詞而是由于ECMAScript語言規(guī)范中規(guī)定了當用let、const聲明時如果標識符是let則報錯。

該代碼是運行在非嚴格模式下的,嚴格模式則報錯,值得注意的是嚴格模式下上面三句話都是因為標識符let是保留字而報錯的。有興趣可以在嚴格模式和非嚴格模式下測試let let = 1;報錯原因是不同的。

下面的所有代碼都在非嚴格模式下進行,如果是嚴格模式我會明確指出。

那么上面三句話中的標識符let改為const會怎么樣?無論是嚴格模式還是非嚴格模式都報錯,錯誤原因是因為const是關鍵字,這時候問題又來了,為什么標識符let和const的行為會不同呢?這個鍋說到底還是得ES5規(guī)范背,在ES5規(guī)范中const被認為是未來保留字(FutureReservedWords)而let只有在嚴格模式下才被認為是未來保留字,這導致var可以聲明let卻不能聲明const,那到了ES6時代為什么不改呢?哎!不是不改而是心有力而余不足啊,鬼知道在ES6時代之前有多少代碼中出現(xiàn)過var let這個聲明啊,這要是改了得有多少網(wǎng)站得炸啊。

基于上面的原因,你看到下面的代碼時不要驚訝:

var let = 1;console.log(let);                  //1let a = 2;console.log(a);                   //2//看著怪異但是完全可以工作,不會有任何錯誤

看完上面一個不同點,我們再看下面這個例子:

var a;console.log(a);                    //undefined//let a;console.log(a);                    //undefined//const a;console.log(a);                    //?

我們都知道如果var和let只聲明變量而不賦值,那么默認賦值undefined,那么const會怎樣呢?
你在Chrome控制臺上試一下就知道了,語法錯誤缺少初始化,ES6規(guī)范指出const聲明的標識符一定要初始化賦值,這不是運行時錯誤,這是個早期錯誤,編譯器在執(zhí)行腳本之前會檢測早期錯誤。

我們接著看下一個問題:

let a = 1;let a = 2;

var可以重復聲明變量,那么let和const可以嗎?答案是不可以。你可以認為let和const聲明的變量名稱在該作用域內(nèi)是唯一的,不能重復聲明。那如果用var可以覆蓋let聲明的變量嗎?答案是不能。不管你是let或const先聲明變量var后面重復聲明,還是var先聲明變量let或const后聲明都會報錯。這個錯誤是一個早期錯誤。

注意:let/const跨腳本聲明重復變量也會報錯。但這個時候的錯誤被認為是運行時錯誤,不是早期錯誤。上面所指的let/const聲明都指在同一作用域下。

塊(Block)

上面列出了var、let、const靜態(tài)語義上的區(qū)別。在該小節(jié)中我會講述在javascript內(nèi)部它們之間的不同,不過在此我們先要了解(塊)Block,可以說let、const是因為Block存在的。
不過提到Block之前我們需要花幾分鐘了解幾個名詞:

我拿個例子簡單說明一下:

//全局聲明var a=1;let b=1;const c=1;function foo(){};class Foo{};{   //塊級聲明   var ba=1;   let bb=1;   const bc=1;   class BFoo{};   function bfoo(){}}
  1. LexicallyDeclaredNames(詞法聲明名稱列表):? bb,bc,bfoo,BFoo ?
  2. LexicallyScopedDeclarations(詞法作用域聲明列表):? let bb=1,const bc=1,function bfoo(){},class BFoo{} ?
  3. VarDeclaredNames(var聲明名稱列表):? ba ?
  4. VarScopedDeclarations(var作用域聲明列表):? ba=1 ?
  5. TopLevelLexicallyDeclaredNames(頂級詞法聲明名稱列表):? b,c,Foo ?
  6. TopLevelLexicallyScopedDeclarations(頂級詞法作用域聲明列表):? let b=1,const c=1,class Foo{} ?
  7. TopLevelVarDeclaredNames(頂級var聲明名稱列表):? a,ba,bfoo ?
  8. TopLevelVarScopedDeclarations(頂級var作用域聲明列表):? a=1,ba=1,function foo(){}?

注:? ?結(jié)構(gòu)是ECMAScript中的一個規(guī)范類型,表示一個List,具體你可以認為它是一個類數(shù)組(當然實際肯定不是,只是方便理解)

有沒有看到怪異的地方?function聲明在頂級作用域(TopLevel)中被視為var聲明,而不在頂級作用域也就是Block或catch塊中被認為是詞法聲明,這就導致了一些有趣的事情。
Block只有前四個列表,函數(shù)(function)和腳本(script)只有后四個列表(其實函數(shù)和腳本也只有前四個,不過前四個列表的值取的是后四個列表的值)。Block雖然有自己的作用域但是它和函數(shù)有著本質(zhì)上的區(qū)別。函數(shù)和腳本你可以看成是相互獨立的而Block是屬于function和script的一部分。具體就是Block中的var聲明同時也被認為是頂級聲明,不管你嵌了多少層塊在里面都不會變,因為Block沒有頂級作用域。

理解了上面的8個名稱,我們再來看看Block中的聲明與function和script中有何不同:

  1. LexicallyDeclaredNames中如果包含任何重復項,則語法錯誤。
  2. LexicallyDeclaredNames中出現(xiàn)的任何元素在VarDeclaredNames聲明中出現(xiàn),語法錯誤。

規(guī)則1很正常,LexicallyDeclaredNames這個列表里不能有重復項,即不能重復聲明。
規(guī)則2這就很有意思了,我們上面說到了在Block中function聲明屬于詞法聲明,于是你會在Block中看到:

{  var foo=1;  function foo(){}        //Syntax Error,var和function不能聲明同一個標識符,腳本和函數(shù)中是不存在這個問題的。//我大膽推測一下,可能在不久的將來腳本和函數(shù)中var和function也不能聲明同一個標識符了。}

補充規(guī)則1中function聲明

{  function a(){};    function a(){};      //it's ok,no syntax Error}//-----------------------'use strict';{  function a(){};    function a(){};      //error, syntax Error redeclaration a; }

這里我不得不吐槽一下了,就因為在非嚴格模式下Block中的function可以重復聲明害我以為規(guī)范1我理解錯了,導致我把文檔中有關Block規(guī)范說明部分翻來覆去看了好幾遍,最后我才在規(guī)范文檔的附錄中找到原因:為了實現(xiàn)網(wǎng)頁瀏覽器的兼容性,允許在非嚴格模式下的Block中的function可以重復聲明。

這里有個建議,最好永遠不要在一個作用域內(nèi)同時使用var和let/const聲明,還有不要在Block中使用var聲明,至于Block中的function聲明,除非你確切的知道你需要這個function做什么,否則也不要在Block中使用function。Block中的function是如此的怪異。

1.非嚴格模式下,block中的function聲明的標識符會被提到頂級作用域下,但是只提標識符,并賦值undefined,不提函數(shù)體。你可以把它看成是一個var聲明的變量,具體如下:

console.log(foo);            //undefined{   function foo(){      console.log(1);   }}foo();                      //1

2.非嚴格模式下,block中的function聲明的函數(shù)對象對這個block來說形成了一個閉包,我認為‘閉包’這個詞是最好的解釋:

var a = 'outer a';{   let a = 'inner a';   function foo(){      console.log(a);   }}console.log(a)              //outer afoo();                      //inner a,     not outer a

3.嚴格模式下,block中的function聲明只能在block中訪問到,離開這個block無法訪問:

'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)這種情況是因為ES5之前,block中不能出現(xiàn)function聲明,但是不同的瀏覽器實現(xiàn)不一樣,到了現(xiàn)在只能通過瀏覽器擴展進行填補。在非嚴格模式下,編譯器進行全局聲明實例化是也就是上篇文章中說道的GlobalDeclarationInstantiation方法時會對block、switch中case和default語句中的function聲明進行額外的操作,如果function聲明的標識符在全局環(huán)境下沒有找打其它的詞法聲明名稱即在TopLevelLexicallyDeclaredNames列表中不存在function聲明的標識符,則在全局環(huán)境記錄下創(chuàng)建function綁定,但是設置的值不是聲明的函數(shù)體而是是undefined。函數(shù)中有相似的操作。

block中的一些注意點以及和function還有script中的區(qū)別我大致講了一下。那么block是如何做到有塊級作用域的功能的呢?
我在上一篇文章中講到了執(zhí)行上下文,提到執(zhí)行上下文是編譯器用來跟蹤代碼執(zhí)行時評估的一種規(guī)范設備,每個執(zhí)行上下文都有自己的LexicalEnvironment和VariableEnvironment組件。編譯器在評估Block做了如下操作:

  1. 讓oldEnv成為正在運行的執(zhí)行上下文(running execution context)的LexicalEnvironment。
  2. 讓blockEnv成為一個新的聲明性環(huán)境,它的外部詞法環(huán)境引用指向oldEnv。
  3. 對block中的聲明進行實例化。
  4. 把正在運行的執(zhí)行上下文(running execution context)的LexicalEnvironment設為blockEnv。
  5. 讓blockValue成為執(zhí)行block中的代碼的結(jié)果。
  6. 把正在運行的執(zhí)行上下文(running execution context)的LexicalEnvironment設為oldEnv。
  7. 返回blockValue。

我們看到了執(zhí)行block中代碼時不會新建執(zhí)行上下文,它只是改變了正在運行的執(zhí)行上下文的LexicalEnvironment組件值,block運行完成后又恢復成以前的LexicalEnvironment組件,這指明了block中聲明的變量只在該block中起作用,這也表示為什么block是塊級作用域。這跟函數(shù)不一樣,執(zhí)行函數(shù)時會創(chuàng)建新的執(zhí)行上下文。
我這再說明一下,步驟3中的聲明進行實例化指得是LexicallyScopedDeclarations列表中的聲明,block不會對其中的var聲明進行操作。步驟5中的blockValue指得是block中最后一個語句執(zhí)行后的返回值。

知道了這個,我們來看個let和var在Block中的不同:

for(var i = 0;i < 10;i++){   setTimeout(function(){console.log(i)})}//輸出10個10for(let i=0;i<10;i++){   setTimeout(function(){console.log(i)})}//輸出0到9

我這邊做個簡單說明:

  1. 把全局環(huán)境記錄記gec,for循環(huán)里的環(huán)境記錄記為bec,匿名函數(shù)的環(huán)境記錄記為fec。
  2. gec的外部環(huán)境null,bec的外部環(huán)境gec,fec的外部環(huán)境bec。
  3. 第一個for循環(huán)中函數(shù)輸出i,fec中沒有i的記錄,向外找bec,沒有i的記錄,向外找找gec,發(fā)現(xiàn)i,值為10,所以輸出10個10。
  4. 第二個for循環(huán)中函數(shù)輸出i,fec中沒有i的記錄,向外找bec,找到i的記錄,并輸出i,這個i是當前bec記錄中i的值,每次循環(huán)都會創(chuàng)建一個新的bec記錄。

變量提升(Hoisting)

我們都知道var和function聲明在作用域內(nèi)存在著變量提升,但是let/const或者class呢?究竟有沒有存在變量提升。這個問題存在著爭議,可謂仁者見仁智者見智。

我在上篇文章中提到了全局聲明實例化和block中的block聲明實例化以及沒有提到的function聲明實例化,你會發(fā)現(xiàn)一個關鍵,就是這些操作都是在執(zhí)行代碼之前做的,全局聲明實例化在腳本執(zhí)行之前進行,block聲明實例化在block中的代碼執(zhí)行之前進行,包括函數(shù)也是如此。那么聲明實例化究竟是做什么的呢?

具體的操作就是把存在LexicallyScopedDeclarations、VarScopedDeclarations、TopLevelLexicallyScopedDeclarations和TopLevelVarScopedDeclarations的信息進行操作,存到環(huán)境記錄中。這些詞都是靜態(tài)語義,也就在在腳本執(zhí)行之前就已經(jīng)存儲了。

var a = 1;let b = 1;//執(zhí)行代碼前環(huán)境記錄(Environment Record)綁定了a,b,并給a賦值為undefined,b不賦值。//注:let、const和class只綁定(實例化)不初始化,var和function會進行初始化,function初始化指的就是整個函數(shù)。//執(zhí)行代碼時----------------console.log(a);      //undefined   環(huán)境記錄中有a的這個綁定,并且值是undefined,所以輸出undefinedvar a = 1;//----------------console.log(a);      //Uncaught ReferenceError: a is not defined   環(huán)境記錄中有a的這個綁定,但是沒有值,所以error。//可能a is not defined改為a is not initialized更能讓人容易理解。// not defined容易和undefined混淆。let a = 1;//一個更好的例子var a = 1;{    console.log(a);        //Uncaught ReferenceError: a is not defined,not value 1;    let a = 2;             //let聲明的變量實際上也提升了} 

正是這樣原因?qū)е隆白兞刻嵘贝嬖跔幾h,一部分人認為let、const、class和var一樣,在一開始就已經(jīng)提升了,所以let、const、class存在“變量提升”。有的人認為所謂“變量提升”,是指代碼不報錯,還能運行,而let、const、class會出現(xiàn)錯誤,所以不能算“變量提升”。

ECMAScript規(guī)范一直沒有給出準確的說明,甚至不同版本說法不一樣,在最新的ES8規(guī)范中雖然沒有給出準確的說明,但是規(guī)范定義了一個HoistableDeclaration文法,該文法中包含了FunctionDeclaration、GeneratorDeclaration和AsyncFunctionDeclaration文法。HoistableDeclaration文法又與ClassDeclaration和LexicalDeclaration(let/const的語法規(guī)則)文法組成Declaration文法。

這里是不是可以推斷出ECMAScript規(guī)范認為let、const和class不存在“變量提升”呢。當然這只是我的一個推測。

結(jié)束語

到這里let/const和var的解釋基本就完結(jié)了。我大致的對let/const以及var做了一個區(qū)別介紹,但是還有很多小的細節(jié)不能涵蓋到,如果感興趣想了解更多的話可以查看官方文檔13.2 Block13.3 let/const和var。
算上最開始的javascript強制轉(zhuǎn)化,這是我對ES8文檔講解的第三篇文章,之后我會陸續(xù)發(fā)表一些我對ES8文檔的理解,希望能與人一起交流共進。

轉(zhuǎn)自 https://segmentfault.com/a/1190000012221834

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
ES6新特性
es6快速入門
TS 變量聲明
ES6中的變量聲明
1.變量:var,let,const
JavaScript中變量聲明var、let、const的區(qū)別
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服