JavaScript是按順序執(zhí)行的嗎?聊聊JavaScript中的變量提升
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
作為一位前端開發(fā)者,我們經(jīng)常會(huì)聽到這么一句話:“JavaScript的執(zhí)行是按照順序自上而下依次執(zhí)行的。”這句話說的并沒有錯(cuò)。但是它似乎又好像不完全對。我們先來看以下這段代碼。你覺得結(jié)果會(huì)輸出什么? 1 showName() 2 console.log(myName) 3 4 var myName = '修謙' 5 function showName() { 6 console.log('我的名字叫修謙') 7 } 若是按照之前說的自上而下依次執(zhí)行的邏輯話,那么應(yīng)該輸出的結(jié)果應(yīng)該是:
然而當(dāng)我們在瀏覽器控制臺(tái)執(zhí)行的時(shí)候,其實(shí)際的結(jié)果卻如下圖所示。
代碼竟然沒有報(bào)錯(cuò)!第一行輸出了“我的名字叫修謙”,第 2 行則輸出了“undefined”,這時(shí)候你是否會(huì)有疑問:“這怎么和前面想象中的順序執(zhí)行有點(diǎn)不一樣啊!怎么結(jié)果會(huì)是這樣的呢?” 到這里,我想你應(yīng)該想到了一點(diǎn)什么。那就是:“函數(shù)和變量是可以在定義之前使用的”。但是我們?nèi)绻麍?zhí)行未定義的函數(shù)和變量的話,又會(huì)是一個(gè)什么樣的結(jié)果呢? 我們嘗試著將之前的第三行代碼刪掉,然后執(zhí)行。 1 showName() 2 console.log(myName) 3 4 function showName() { 5 console.log('我的名字叫修謙') 6 } 運(yùn)行代碼后,如下圖所示,這一次我們看到的結(jié)果是函數(shù)已經(jīng)執(zhí)行了,但是console函數(shù)輸出的已經(jīng)報(bào)錯(cuò)了,輸出了“myName is not defined”。
到這里,對于以上的兩個(gè)結(jié)果,你是否又能得到了一些新的啟示呢?事實(shí)上,通過上面的兩次代碼執(zhí)行,我們至少可以得到以下幾個(gè)結(jié)論:
第一個(gè)結(jié)論我們很容易理解,因?yàn)樽兞课幢欢x,所以在使用的時(shí)候肯定是找不到,因此必然會(huì)報(bào)錯(cuò)。但是對于第二個(gè)和第三個(gè)結(jié)論,確實(shí)讓人費(fèi)解的:變量和函數(shù)為什么能在其定義之前使用?這似乎表明JS代碼并不是按之前說的自上而下依次執(zhí)行的。 另外一點(diǎn),就是同樣的方式,變量和函數(shù)的處理結(jié)果為什么不一樣?如上面的執(zhí)行結(jié)果,提前使用的showName函數(shù)能打印出來完整結(jié)果,但是提前使用的myName變量值卻是undefined,而不是我們定義時(shí)使用的“修謙”的這個(gè)值。要解釋這個(gè),就不得不說到JavaScript中的一個(gè)很重要的概念:變量提升 1、什么是JavaScript的變量提升(Hoisting)在說JavaScript的變量提升之前,我們得要先說一下JavaScript中的聲明和賦值操作,對于如下的這行代碼 1 var myName = '修謙' 實(shí)際上,這句代碼你可以把它分為兩部分來看,即聲明和賦值 1 var myName // 變量聲明 2 myName = '修謙' // 變量賦值 以上的這個(gè)是JavaScript中變量的聲明和賦值,我們再來看一下JavaScript中的函數(shù)聲明和賦值操作是什么樣的,我們還是看以下這段代碼 1 function showName() { 2 console.log('我的名字叫修謙') 3 } 4 5 var showName = function() { 6 console.log('我的名字叫修謙') 7 } 我們可以看出第一個(gè)函數(shù)showName是一個(gè)完整的函數(shù)聲明,它沒有涉及到賦值操作;第二個(gè)函數(shù)是先聲明變量showName,再把function(){console.log('我的名字叫修謙')}賦值給了showName。到這里你應(yīng)該知道了JavaScript中的變量聲明和賦值是怎么回事了。 說完了JavaScript中的變量聲明和賦值是怎么回事后,我們再來說JavaScript中的變量提升。 在JavaScript中,所謂的變量提升:是指在 JavaScript 代碼執(zhí)行過程中,JavaScript 引擎把變量的聲明部分和函數(shù)的聲明部分提升到代碼開頭的一種“行為”。當(dāng)變量被提升后,會(huì)給變量設(shè)置默認(rèn)值,而其所設(shè)置的默認(rèn)值就是我們最為熟悉的undefined。從這個(gè)概念的字面意義上來看,“變量提升”意味著變量和函數(shù)的聲明會(huì)在物理層面移動(dòng)到代碼的最前面。 但其實(shí)這樣說也并不準(zhǔn)確。因?yàn)閷?shí)際上,在JavaScript中,變量和函數(shù)的聲明在代碼里的位置是不會(huì)改變的。為什么呢?因?yàn)樵贘avaScript中,一段代碼的執(zhí)行是需要先經(jīng)過JavaScript引擎先編譯的,當(dāng)代碼編譯完后,才會(huì)進(jìn)入到代碼的執(zhí)行階段(下圖所示)。說變量和函數(shù)的聲明在代碼里的位置是不會(huì)改變的原因,是因?yàn)榇a在編譯階段便已經(jīng)被JavaScript引擎放入到了內(nèi)存中(既然放到了內(nèi)存當(dāng)中,那么其位置當(dāng)然就已經(jīng)固定)。 那既然在編譯階段就在內(nèi)存中固定了位置,為什么又會(huì)出現(xiàn)提升呢?編譯階段和變量提升存在什么關(guān)系呢?這里我們就不得不說到另外一個(gè)概念:執(zhí)行上下文(Execution context) 2、執(zhí)行上下文(Execution context)所謂執(zhí)行上下文,我們可以簡單的理解為就是 JavaScript 執(zhí)行一段代碼時(shí)的運(yùn)行環(huán)境,比如當(dāng)我們在JavaScript文件中調(diào)用一個(gè)函數(shù),那么就會(huì)進(jìn)入這個(gè)函數(shù)的執(zhí)行上下文,就會(huì)確定該函數(shù)在執(zhí)行期間用到的諸如 this、變量、對象以及函數(shù)等。并且在執(zhí)行上下文中還存在一個(gè)變量環(huán)境的對象(Viriable Environment),這是非常重要的。因?yàn)樵搶ο笾斜4媪俗兞刻嵘膬?nèi)容,比如上面代碼中的變量myName和函數(shù)showName,都會(huì)保存在該對象中(我們先用下面的這段代碼模擬一下,后面在詳細(xì)講解)。 1 ViriableEnvironment(變量環(huán)境) 2 myName -> undefined 3 showName -> function: {console.log(myName)} 在JavaScript中,執(zhí)行上下文一般分為以下三種: 1、全局執(zhí)行上下文:當(dāng) JavaScript 執(zhí)行全局代碼的時(shí)候,會(huì)編譯全局代碼并創(chuàng)建全局執(zhí)行上下文,而且在整個(gè)頁面的生存周期內(nèi),全局執(zhí)行上下文只有一份。 2、函數(shù)執(zhí)行上下文:當(dāng)調(diào)用一個(gè)函數(shù)的時(shí)候,函數(shù)體內(nèi)的代碼會(huì)被編譯,并創(chuàng)建函數(shù)執(zhí)行上下文,在一般情況下,函數(shù)執(zhí)行結(jié)束之后,創(chuàng)建的函數(shù)執(zhí)行上下文會(huì)被銷毀。 3、eval :當(dāng)使用 eval 函數(shù)的時(shí)候,eval 的代碼也會(huì)被編譯,并創(chuàng)建執(zhí)行上下文。 但是我們現(xiàn)在常接觸或者說的一般都是指前面兩者。了解完執(zhí)行上下文的概念和分類后,我們再來了解一下另外的兩個(gè)知識(shí)點(diǎn):函數(shù)執(zhí)行(調(diào)用)和棧 3、函數(shù)執(zhí)行(調(diào)用)函數(shù)調(diào)用概念很簡單,簡單一點(diǎn)來說就是運(yùn)行一個(gè)函數(shù),具體使用方式是使用函數(shù)名稱跟著一對小括號(hào)。我們舉個(gè)例子來說一下 1 var myName = '修謙' 2 function showName() { 3 console.log('我的名字叫修謙') 4 } 5 6 showName() // 執(zhí)行 這段代碼很簡單。首先我們創(chuàng)建了一個(gè)名叫myName的變量,接著又創(chuàng)建了一個(gè)showName的函數(shù)。完后緊接著在最后面調(diào)用執(zhí)行了該方法。下面我們就以這段簡單的代碼來說一下函數(shù)調(diào)用的過程。 當(dāng)執(zhí)行到函數(shù)showName()之前,JavaScript 引擎會(huì)為上面這段代碼創(chuàng)建全局執(zhí)行上下文,包含聲明的函數(shù)和變量,如下圖所示: 從圖中可以看出,上面那段代碼中全局變量和函數(shù)都保存在全局上下文的變量環(huán)境中。當(dāng)執(zhí)行上下文準(zhǔn)備好之后,JavaScript引擎便開始執(zhí)行全局代碼,當(dāng)執(zhí)行到showName函數(shù)時(shí),JavaScript判斷出這是一個(gè)函數(shù)調(diào)用,于是便開始了以下操作:
我們可以用一張相對完整的圖來描述 當(dāng)執(zhí)行到showName函數(shù)的時(shí)候,我們就有了兩個(gè)執(zhí)行上下文了——全局執(zhí)行上下文和 showName 函數(shù)本身的執(zhí)行上下文(函數(shù)執(zhí)行上下文)。這也就是說在執(zhí)行JavaScript 時(shí),會(huì)存在多個(gè)執(zhí)行上下文。那當(dāng)有多個(gè)上下文的時(shí)候,JavaScript引擎是如何管理的呢?這就是我們下面要說到的一種數(shù)據(jù)結(jié)構(gòu)——棧 4、棧(Stack)棧(Stack)是一種線性數(shù)據(jù)結(jié)構(gòu),遵循后進(jìn)先出(Last In, First Out, LIFO)的原則。這意味著最后進(jìn)入棧的元素會(huì)最先被移除。如下圖所示,最先進(jìn)入的是A,但是最先出的卻是E。而avaScript引擎正是利用棧的這種結(jié)構(gòu)來管理執(zhí)行上下文的。在執(zhí)行上下文創(chuàng)建好后,JavaScript引擎會(huì)將執(zhí)行上下文壓入棧中,然后進(jìn)行執(zhí)行,而通常把這種用來管理執(zhí)行上下文的棧稱為執(zhí)行上下文棧,或者叫JavaScript調(diào)用棧。 4、執(zhí)行上下文棧(JavaScript調(diào)用棧)下面我們就來具體的用代碼和圖來模擬JS執(zhí)行上下文棧是如何執(zhí)行代碼的,如下面一段代碼(以ES5來演示) 1 var a = 2 2 function add(b,c){ 3 return b+c 4 } 5 function addAll(b,c){ 6 var d = 2 7 result = add(b,c) 8 return a+result+d 9 } 10 addAll(3,3) 第一步:創(chuàng)建全局上下,并將其壓入棧底(如圖所示)此時(shí)變量a、函數(shù)add 以及 addAll 都保存到了全局上下文的變量環(huán)境對象中。 當(dāng)全局執(zhí)行上下文壓入到調(diào)用棧后,緊接著,JavaScript引擎便開始執(zhí)行全局代碼了。首先會(huì)執(zhí)行a=2的賦值操作,賦值完后,此前a的值就從undefined變成了2。因?yàn)榇藭r(shí)的add函數(shù)和addAll函數(shù)都還沒有執(zhí)行,因此狀態(tài)還是之前的。這一步完成后,我們再來看全局上下文的狀態(tài),如下圖所示: 第二步:執(zhí)行addAll函數(shù),此時(shí)JavaScript引擎會(huì)編譯該函數(shù),并為其創(chuàng)建一個(gè)執(zhí)行上下文,然后將其執(zhí)行上下文壓入棧中,如圖所示: 同樣的,當(dāng)addAll函數(shù)的執(zhí)行上下文創(chuàng)建好之后,就會(huì)進(jìn)入了函數(shù)代碼的執(zhí)行階段了,因?yàn)楹瘮?shù)中有一個(gè)變量d,因此還是先執(zhí)行賦值操作,即將d的值從之前的d=undefined設(shè)置成d=10 。然后接著往下執(zhí)行。 第三步:執(zhí)行add函數(shù),當(dāng)執(zhí)行到add函數(shù)調(diào)用語句時(shí),JavaScript引擎同樣又會(huì)為其創(chuàng)建執(zhí)行上下文,并將其壓入調(diào)用棧,此時(shí)的調(diào)用棧的狀態(tài)如下圖所示: 然后add函數(shù)執(zhí)行,將返回結(jié)果賦值給變量result,此時(shí)的result的值便從之前的undefined變成了6。隨后該函數(shù)的執(zhí)行上下文便從從棧頂彈出。此時(shí)的調(diào)用棧如下圖所示: 緊接著addAll執(zhí)行最后一個(gè)相加操作后并返回,完成之后,addAll的執(zhí)行上下文也會(huì)從棧頂部彈出,此時(shí)調(diào)用棧中就只剩下全局上下文了。最終如下圖所示。 至此,整個(gè)JavaScript的執(zhí)行便完成了。 通過以上的分析,我們可以知道,正是由于JavaScript存在變量提升這種特性,從而導(dǎo)致了我們在日常的學(xué)習(xí)或者工作中,總是能看到很多與直覺不符或者說與我們思習(xí)慣不一樣的代碼,而這也是JavaScript的一個(gè)重要設(shè)計(jì)缺陷。為此, ECMAScript6引入塊級(jí)作用域的概念并配合 let、const 關(guān)鍵字,來避開了這種設(shè)計(jì)缺陷(這個(gè)我們接下來就會(huì)說)。但是在說之前,我們還要繼續(xù)說變量提升剩余的兩個(gè)問題:為什么JS中會(huì)出現(xiàn)變量提升?變量提升有什么缺點(diǎn)? 5、JS中變量提升的原因我們都知道在ES6 之前,JavaScript是不支持塊級(jí)作用域的。因?yàn)楫?dāng)初設(shè)計(jì)這門語言的時(shí)候,只是按照最簡單的方式來設(shè)計(jì)的。即只設(shè)計(jì)了全局作用域和函數(shù)作用域。以此來簡化JavaScript代碼的解析和執(zhí)行過程??蓻]有想到的是 JavaScript后面會(huì)這么火,最后其沒有塊級(jí)作用域的缺陷便慢慢暴露了出來。 既然問題已經(jīng)暴露出來了的話,那就解決問題。但是你不可能貿(mào)然的立馬增加塊級(jí)作用域吧!畢竟已經(jīng)用JavaScript這門語言開發(fā)了那么多應(yīng)用。于是就采取了一個(gè)不是特別激進(jìn)的方法——把作用域內(nèi)部的變量統(tǒng)一提升。這也是彼時(shí)最快速,也是最簡單的方式。 當(dāng)然了任何事物都有兩面性。這一做法的一個(gè)很大的缺點(diǎn)就是直接導(dǎo)致了函數(shù)中的變量無論是在哪里聲明的,在編譯階段都會(huì)被提取到執(zhí)行上下文的變量環(huán)境中,所以這些變量在整個(gè)函數(shù)體內(nèi)部的任何地方都是能被訪問的,而這也就是我們通常說的JS 中的變量提升。 6、JS中變量提升的問題1、變量在不知不覺中就被覆蓋 我們先來看下面的一段代碼,你認(rèn)為會(huì)輸出什么結(jié)果?是修謙?是吳門山人? 1 var myName = "修謙" 2 3 function showName(){ 4 console.log(myName); 5 if(0){ 6 var myName = "吳門山人" 7 } 8 console.log(myName); 9 } 10 11 showName() 其實(shí)你把代碼執(zhí)行的話,會(huì)發(fā)現(xiàn)其輸出的結(jié)果兩者都不是。而是輸出了undefined。為什么會(huì)這樣呢?你可以參照前面舉的那個(gè)JS執(zhí)行的例子來自己試著畫一下過程圖。這里我們就直接貼最后的執(zhí)行棧圖。 當(dāng)showName函數(shù)的執(zhí)行上下文創(chuàng)建后,JavaScript引擎便開始執(zhí)行其內(nèi)部的代碼。首先執(zhí)行的是console.log(myName)。而執(zhí)行這段代碼需要使用變量myName,而從圖上我們可以看到,這里有兩個(gè)myName變量:一個(gè)是在全局執(zhí)行上下文中,其值是“修謙”;另外一個(gè)則是在showName函數(shù)的執(zhí)行上下文中,其值是undefined。這個(gè)時(shí)候JS到底要使用哪一個(gè)輸出呢?作為一個(gè)前端開發(fā)人員,我想絕大部分人都會(huì)說出正確的答案:“肯定是先使用showName函數(shù)執(zhí)行上下文里面的變量啦!” 的確是這樣,因?yàn)楹瘮?shù)執(zhí)行過程中,JavaScript會(huì)優(yōu)先從當(dāng)前所在的執(zhí)行上下文中查找變量,但是因?yàn)樽兞刻嵘脑?,?dāng)前的執(zhí)行上下文中就包含了變量myName,而其值是undefined,所以獲取到的myName的值就是undefined。而不是如其它語言一樣,會(huì)輸出“修謙”。 2. 本應(yīng)銷毀的變量沒有被銷毀 那既然在JavaScript中,變量提升會(huì)帶來上面說到的那些個(gè)問題?最后的解決方案又是什么呢?答案就是在2015年的時(shí)候發(fā)布了新的JS標(biāo)準(zhǔn)——ECMAScript6(簡稱ES6)。在 該標(biāo)準(zhǔn)中,正式引入了塊級(jí)作用域的概念。并且還引入了 let 和 const 關(guān)鍵字來聲明塊級(jí)作用域,至此,JavaScript也能像其他語言一樣擁有了塊級(jí)作用域。 7、ES6中的let和const關(guān)于let和const。我們還是先來看如下的代碼 1 let myName = '修謙' 2 const myAag = 35 3 myName = '山人' 4 console.log(myName) 5 6 myAag = 18 7 console.log(myAag) 這段代碼輸出的結(jié)果,我覺得只要是寫過JavaScript的人都應(yīng)該知道結(jié)果是啥。第一個(gè)輸出的是“山人”;而第二個(gè)則輸出一個(gè)錯(cuò)誤。從這里我們可以看出,雖然兩者都是用來聲明塊級(jí)作用域的,但是兩者之間還是有區(qū)別的,使用 let 關(guān)鍵字聲明的變量是可以被改變的,而使用 const 聲明的變量其值是不可以被改變的。說到這里我們也順帶說一下面試中常被問到的一個(gè)問題:在JavaScript中,什么是暫時(shí)性死區(qū)? 還是先看代碼 1 function example() { 2 console.log(x); 3 let x = 10; 4 } 5 6 example(); 當(dāng)我們把這段代碼復(fù)制到到瀏覽器控制臺(tái)的時(shí)候會(huì)報(bào)這樣一個(gè)錯(cuò)誤: “ReferenceError: Cannot access 'x' before initialization”。這個(gè)錯(cuò)誤翻譯過來是:引用錯(cuò)誤:初始化之前無法訪問“x”(翻譯的可能不準(zhǔn),但是意思差不多)。從這個(gè)錯(cuò)誤我們知道了在ES6中,當(dāng)我們用let和const 聲明的變量在聲明之前是處于一種“未初始化”狀態(tài),而這種狀態(tài)被稱為暫時(shí)性死區(qū)(官方的定義是:在 JavaScript 中,"暫時(shí)性死區(qū)"(Temporal Dead Zone, TDZ)是指在塊級(jí)作用域(如 let 和 const 聲明的變量所在的代碼塊)中,在變量聲明之前訪問該變量會(huì)導(dǎo)致引用錯(cuò)誤(ReferenceError))。 說完let和const,我們再來看以下的這兩行簡單的代碼 1 var myName = '修謙' 2 let myAag = 35 這兩行代碼其實(shí)并沒有什么特別的,我用其來就只是為了引出一個(gè)問題,即:JavaScript是怎么樣在支持變量提升特性的同時(shí)又支持塊級(jí)作用域的呢?因?yàn)槲覀冊陧?xiàng)目中,有時(shí)候你會(huì)發(fā)現(xiàn)有的人在代碼中即會(huì)用var關(guān)鍵字來聲明變量,同時(shí)又用let和const來聲明變量。雖然這種方式不推薦,但是總歸是不可避免的。前面我們已經(jīng)談到了變量提升特性。所以接下來我們重點(diǎn)談的就是JavaScript是如何支持塊級(jí)作用域的。 8、JavaScript 是如何支持塊級(jí)作用域的?前面我們說到,在JavaScript引擎中是通過變量環(huán)境實(shí)現(xiàn)函數(shù)級(jí)作用域的,那么在 ES6 中,又是如何在其基礎(chǔ)之上,實(shí)現(xiàn)對塊級(jí)作用域的支持呢?我們還是先來看下面的一段代碼 1 function showName(){ 2 var myName = '修謙' 3 let myAag = 35 4 { 5 let myAag = 18 6 var heName = '華仔' 7 let heAge = 63 8 console.log(myName) 9 console.log(myAag) 10 } 11 } 12 showName() 當(dāng)執(zhí)行上面這段代碼的時(shí)候,JavaScript引擎會(huì)先對其進(jìn)行編譯并創(chuàng)建執(zhí)行上下文,然后再按照順序執(zhí)行代碼,之前我們的例子是沒有使用ES6中的關(guān)鍵字let。但是現(xiàn)在引入了 let 關(guān)鍵字,它會(huì)創(chuàng)建塊級(jí)作用域,那么它是如何影響執(zhí)行上下文的呢?這里我們就不得不提到一個(gè)名詞——詞法環(huán)境。你應(yīng)該還記得之前的例子中,右邊一直有一塊空著的,名叫詞法環(huán)境的塊。而JavaScript之所以支持塊級(jí)作用域,就是與它有關(guān)。 下面我們還是按照之前的方式來梳理一下這段代碼的執(zhí)行。 第一步:編譯并創(chuàng)建全局執(zhí)行上下文 第二步:執(zhí)行showName函數(shù),為其創(chuàng)建函數(shù)執(zhí)行上下文 到showName函數(shù)執(zhí)行這一步。我么可以從調(diào)用棧中看出:
第三步:繼續(xù)往下執(zhí)行代碼。當(dāng)執(zhí)行到代碼塊里面時(shí),變量環(huán)境中myName的值已經(jīng)被設(shè)置成了"修謙",而詞法環(huán)境中myAag的值則被設(shè)置成了35,此時(shí)的函數(shù)的執(zhí)行上下文如下圖所示: 從第三步的圖中我們可以看出,當(dāng)進(jìn)入函數(shù)內(nèi)部的作用域塊時(shí),作用域塊中通過 let 聲明的變量(myAag和heAag),會(huì)被存放在詞法環(huán)境的一個(gè)單獨(dú)的區(qū)域中,且不影響作用域塊外面的變量(之前的myAag)。因此它們都是獨(dú)立的存在。另外我們從中也可以看出,其實(shí)在詞法環(huán)境內(nèi)部,也是維護(hù)了一個(gè)小型棧結(jié)構(gòu),棧底是函數(shù)最外層的變量(即內(nèi)部作用域塊外邊的變量,這里就是myAag),當(dāng)進(jìn)入某一個(gè)作用域塊后,就會(huì)把該作用域塊內(nèi)部的變量壓到棧頂(myAag和heAag);當(dāng)作用域執(zhí)行完成之后,該作用域的信息就會(huì)從棧頂彈出,而這就是詞法環(huán)境的結(jié)構(gòu)(前提就是必須用let或者const關(guān)鍵字定義)。 第四步:繼續(xù)往下執(zhí)行代碼。將作用塊中的myAag和heAag分別賦值為16,63,同時(shí)也將環(huán)境變量中的heName的值賦值為“華仔”。如圖所示 第五步:繼續(xù)往下執(zhí)行代碼。當(dāng)執(zhí)行到作用域塊中的console.log(myName)這行代碼時(shí),此時(shí)就需要在詞法環(huán)境和變量環(huán)境中查找變量myName的值了,而具體查找方式是:沿著當(dāng)前詞法環(huán)境的棧頂向下查詢,如果在詞法環(huán)境中的某個(gè)塊中查找到了,就直接返回給JavaScript引擎,如果沒有查找到,那么繼續(xù)在變量環(huán)境中查找(同樣的,作用域塊中的console.log(myAag)也是這樣的規(guī)則)。此時(shí)如下圖所示:因?yàn)樵谠~法環(huán)境中沒有找到myName的這個(gè)變量,因此就會(huì)去變量環(huán)境中去找,最終在變量環(huán)境中找到了myName(黃色箭頭所指),因此輸出“修謙”。同樣console.log(myAag)因?yàn)樵谠~法環(huán)境中找到了myAag(深藍(lán)色箭頭所指),因此輸出18。而將上面的代碼在瀏覽器里執(zhí)行,也是這樣的結(jié)果
當(dāng)函數(shù)內(nèi)部作用域塊執(zhí)行結(jié)束之后,其內(nèi)部定義的變量就會(huì)從詞法環(huán)境的棧頂彈出,最終的執(zhí)行上下文如下圖所示: 通過上面的分析,我們基本已經(jīng)理解了詞法環(huán)境的結(jié)構(gòu)和工作機(jī)制:ES6中的塊級(jí)作用域就是通過詞法環(huán)境的棧結(jié)構(gòu)來實(shí)現(xiàn)的,而之前的變量提升是通過變量環(huán)境來實(shí)現(xiàn),通過這兩者的結(jié)合,JS 引擎也就同時(shí)支持了變量提升和塊級(jí)作用域了。至此,我想關(guān)于變量提升,你應(yīng)該有一個(gè)比較深刻的印象了。當(dāng)然了,上面寫的可能并不完全正確。也歡迎大家指正批評。 ?轉(zhuǎn)自https://www.cnblogs.com/xiuqian/p/18595873 該文章在 2024/12/17 15:53:47 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |