日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

javascript中var、let、const声明的区别

發(fā)布時間:2025/4/16 javascript 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 javascript中var、let、const声明的区别 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

我在上一篇文章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); //1 let 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(){} }
  • LexicallyDeclaredNames(詞法聲明名稱列表):? bb,bc,bfoo,BFoo ?
  • LexicallyScopedDeclarations(詞法作用域聲明列表):? let bb=1,const bc=1,function bfoo(){},class BFoo{} ?
  • VarDeclaredNames(var聲明名稱列表):? ba ?
  • VarScopedDeclarations(var作用域聲明列表):? ba=1 ?
  • TopLevelLexicallyDeclaredNames(頂級詞法聲明名稱列表):? b,c,Foo ?
  • TopLevelLexicallyScopedDeclarations(頂級詞法作用域聲明列表):? let b=1,const c=1,class Foo{} ?
  • TopLevelVarDeclaredNames(頂級var聲明名稱列表):? a,ba,bfoo ?
  • TopLevelVarScopedDeclarations(頂級var作用域聲明列表):? a=1,ba=1,function foo(){}?
  • 注:? ?結構是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中有何不同:

  • LexicallyDeclaredNames中如果包含任何重復項,則語法錯誤。
  • 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 a foo(); //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做了如下操作:

  • 讓oldEnv成為正在運行的執(zhí)行上下文(running execution context)的LexicalEnvironment。
  • 讓blockEnv成為一個新的聲明性環(huán)境,它的外部詞法環(huán)境引用指向oldEnv。
  • 對block中的聲明進行實例化。
  • 把正在運行的執(zhí)行上下文(running execution context)的LexicalEnvironment設為blockEnv。
  • 讓blockValue成為執(zhí)行block中的代碼的結果。
  • 把正在運行的執(zhí)行上下文(running execution context)的LexicalEnvironment設為oldEnv。
  • 返回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

    我這邊做個簡單說明:

  • 把全局環(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。
  • 第一個for循環(huán)中函數(shù)輸出i,fec中沒有i的記錄,向外找bec,沒有i的記錄,向外找找gec,發(fā)現(xiàn)i,值為10,所以輸出10個10。
  • 第二個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,所以輸出undefined var 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不存在“變量提升”呢。當然這只是我的一個推測。

    結束語

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

    總結

    以上是生活随笔為你收集整理的javascript中var、let、const声明的区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。