詳解JavaScript中的執(zhí)行上下文
引言
當(dāng)我們?cè)跒g覽器中運(yùn)行JavaScript
代碼時(shí),瀏覽器會(huì)先創(chuàng)建一個(gè)全局執(zhí)行上下文(Global Execution Context
),然后逐行解析和執(zhí)行代碼。
執(zhí)行上下文是JavaScript
中非常重要的概念,它決定了代碼的執(zhí)行順序和作用域鏈等重要信息。了解執(zhí)行上下文的概念和工作原理,對(duì)于理解JavaScript
的運(yùn)行機(jī)制和調(diào)試錯(cuò)誤非常有幫助。
在本文中,我們將深入探討JavaScript
的執(zhí)行上下文,從而幫助讀者更好地理解JavaScript
的運(yùn)行機(jī)制。
1、什么是執(zhí)行上下文
一般來(lái)說(shuō),聽(tīng)到上下文
這個(gè)東西,很自然想到了語(yǔ)文老師講到的在上下文中找到相關(guān)聯(lián)的段落和句子...
其實(shí)在JS
中的上下文更多的是一個(gè)抽象的概念。它具體是指在當(dāng)前執(zhí)行環(huán)境中的變量、函數(shù)聲明,參數(shù)(arguments),作用域鏈,this等信息。
1.1、瀏覽器如何理解執(zhí)行JavaScript
瀏覽器并不理解我們?cè)趹?yīng)用中編寫(xiě)的高級(jí)JavaScript
代碼。代碼需要被轉(zhuǎn)換成瀏覽器和計(jì)算機(jī)能夠理解的格式——機(jī)器碼
。
瀏覽器在讀取HTML
時(shí),如果遇到了<script>
標(biāo)簽或包含JavaScript
代碼的屬性如onClick
,會(huì)發(fā)送給JavaScript引擎
。
瀏覽器的JavaScript引擎
會(huì)創(chuàng)造一個(gè)特殊的環(huán)境來(lái)處理這些JavaScript
代碼的轉(zhuǎn)換和執(zhí)行。這個(gè)特殊的環(huán)境被稱(chēng)為執(zhí)行上下文
。
執(zhí)行上下文包含當(dāng)前正在運(yùn)行的代碼和有助于其執(zhí)行的所有內(nèi)容。在執(zhí)行上下文運(yùn)行期間,編譯器
解析代碼,內(nèi)存存儲(chǔ)變量和函數(shù),可執(zhí)行的字節(jié)碼生成后,代碼執(zhí)行。
實(shí)在不好理解,先入為主,將之想象成一個(gè)執(zhí)行JS的容器
。
1.2、執(zhí)行上下文
執(zhí)行上下文
是JavaScript
中非常重要的概念,它代表了代碼執(zhí)行時(shí)的環(huán)境。每當(dāng)JavaScript引擎
執(zhí)行一段代碼時(shí),都會(huì)創(chuàng)建一個(gè)執(zhí)行上下文。執(zhí)行上下文包含了三個(gè)重要的組成部分:變量對(duì)象
、作用域鏈
和this值
。
- 變量對(duì)象:是當(dāng)前執(zhí)行上下文中的變量、函數(shù)聲明和函數(shù)參數(shù)的存儲(chǔ)空間。
- 作用域鏈:是當(dāng)前執(zhí)行上下文中所有父級(jí)執(zhí)行上下文的變量對(duì)象的集合,它決定了當(dāng)前執(zhí)行上下文中變量的可訪(fǎng)問(wèn)性。
- this值:代表當(dāng)前函數(shù)的執(zhí)行環(huán)境。
2、執(zhí)行上下文有哪些類(lèi)型呢
JavaScript
中有三種執(zhí)行上下文類(lèi)型
1.全局執(zhí)行上下文(GEC)
任何不在函數(shù)內(nèi)部的代碼都在全局上下文中。一個(gè)程序中只會(huì)有一個(gè)全局執(zhí)行上下文。
2.函數(shù)執(zhí)行上下文(FEC)
每當(dāng)一個(gè)函數(shù)被調(diào)用時(shí), 都會(huì)為該函數(shù)創(chuàng)建一個(gè)新的上下文。每個(gè)函數(shù)都有它自己的執(zhí)行上下文,它在函數(shù)被調(diào)用時(shí)創(chuàng)建。函數(shù)上下文可以有任意多個(gè)。
3.Eval函數(shù)執(zhí)行上下文
執(zhí)行在eval
函數(shù)內(nèi)部的代碼也會(huì)有它屬于自己的執(zhí)行上下文。eval
不經(jīng)常被使用到。
小知識(shí):
eval()
函數(shù)計(jì)算JavaScript
字符串,并把它作為腳本代碼來(lái)執(zhí)行。
如果參數(shù)是一個(gè)表達(dá)式,eval()
函數(shù)將執(zhí)行表達(dá)式。如果參數(shù)是Javascript
語(yǔ)句,eval()
將執(zhí)行Javascript
語(yǔ)句。
主要的還是全局執(zhí)行上下文和函數(shù)執(zhí)行上下文。
3、執(zhí)行上下文的生命周期
在JavaScript中,執(zhí)行上下文的生命周期可以分為三個(gè)階段:創(chuàng)建階段、執(zhí)行階段和銷(xiāo)毀階段。
3.1、創(chuàng)建階段
在創(chuàng)建階段,執(zhí)行上下文首先與執(zhí)行上下文對(duì)象(ECO)
相關(guān)聯(lián)。執(zhí)行上下文對(duì)象存儲(chǔ)了許多重要的數(shù)據(jù),執(zhí)行上下文中的代碼在運(yùn)行時(shí)會(huì)使用這些數(shù)據(jù)。創(chuàng)建階段分三個(gè)步驟來(lái)定義和設(shè)置執(zhí)行上下文對(duì)象的屬性:
- 創(chuàng)建變量對(duì)象(
VO
) - 創(chuàng)建作用域鏈
- 設(shè)置
this
關(guān)鍵字的值
3.1.1、創(chuàng)建變量對(duì)象(VO)
變量對(duì)象(VO)
是一個(gè)在執(zhí)行上下文中創(chuàng)建的類(lèi)似于對(duì)象的容器,存儲(chǔ)執(zhí)行上下文中變量
和函數(shù)聲明
。
在GEC
中,每當(dāng)使用var
關(guān)鍵字聲明變量,VO
就會(huì)添加一個(gè)指向該變量的屬性,并將值設(shè)置為undefined
。每當(dāng)函數(shù)聲明時(shí),VO
就會(huì)添加一個(gè)指向該函數(shù)的屬性,并將這個(gè)屬性存儲(chǔ)在內(nèi)存中。這就意味著在開(kāi)始運(yùn)行代碼之前,所有函數(shù)聲明就已經(jīng)存儲(chǔ)在VO
中,并可以在VO
中訪(fǎng)問(wèn)。
但在FEC
中并不創(chuàng)建VO
,而是生成一個(gè)類(lèi)數(shù)組對(duì)象,稱(chēng)為arguments對(duì)象
,在下文稱(chēng)AO
,包含傳入函數(shù)的所有參數(shù)。
小知識(shí):
這種將變量和函數(shù)聲明存儲(chǔ)在內(nèi)存中優(yōu)先于執(zhí)行代碼的過(guò)程被稱(chēng)為提升。
3.1.2、創(chuàng)建作用域鏈
JavaScript
中的作用域鏈?zhǔn)且粋€(gè)機(jī)制,決定了一段代碼對(duì)于代碼庫(kù)中其他一些代碼來(lái)說(shuō)的可訪(fǎng)問(wèn)性。
可以帶著這樣一些問(wèn)題思考:
- 一段代碼可以在哪里訪(fǎng)問(wèn)?哪里不能訪(fǎng)問(wèn)?
- 代碼哪些部分可以被訪(fǎng)問(wèn)?哪些部分不能?
每一個(gè)函數(shù)執(zhí)行上下文都會(huì)創(chuàng)建一個(gè)作用域,作用域相當(dāng)于是一個(gè)空間/環(huán)境,變量和函數(shù)定義在這個(gè)空間里,并且可以通過(guò)一個(gè)叫做作用域查找的過(guò)程訪(fǎng)問(wèn)。如果函數(shù)被定義在另一個(gè)函數(shù)內(nèi)部,處在內(nèi)部的函數(shù)可以訪(fǎng)問(wèn)自己內(nèi)部的代碼以及外部函數(shù)(父函數(shù))的代碼。這種行為被稱(chēng)作詞法作用域查找。但外部函數(shù)并不能訪(fǎng)問(wèn)內(nèi)部函數(shù)的代碼。
小知識(shí):
作用域的概念就引出了JavaScript
另一個(gè)相關(guān)的現(xiàn)象——閉包。閉包指的是內(nèi)部函數(shù)永遠(yuǎn)可以訪(fǎng)問(wèn)外部函數(shù)中的代碼,即便外部函數(shù)已經(jīng)執(zhí)行完畢。
JavaScript
引擎一路向上遍歷執(zhí)行上下文直至解析處在函數(shù)內(nèi)部觸發(fā)的變量和函數(shù)的概念就叫作用域鏈。
3.1.3、設(shè)置this關(guān)鍵字的值
JavaScript
中this
關(guān)鍵字指的是執(zhí)行上下文所屬的作用域。一旦作用域鏈被創(chuàng)建,JS引擎
就會(huì)初始化this
關(guān)鍵字的值。
全局上下文中的this值:
在GEC
(所有函數(shù)和對(duì)象之外)中,this
指向全局對(duì)象——window
對(duì)象。同時(shí),由var
關(guān)鍵字初始化的函數(shù)聲明和變量會(huì)被作為全局對(duì)象(window
對(duì)象)的方法或者屬性。
在任何函數(shù)外聲明的變量和函數(shù),如下:
var name = "jack"; function getName() { console.log('hello') };
與下方的寫(xiě)法是一致的:
window.name = "jack"; window.getName = () => { console.log('hello') };
在GEC
中的函數(shù)和變量會(huì)被當(dāng)作window
對(duì)象的方法和屬性。
函數(shù)中的this:
在FEC
中,并沒(méi)有創(chuàng)建this
對(duì)象,而是能夠訪(fǎng)問(wèn)this
被定義的環(huán)境。
在函數(shù)內(nèi)部訪(fǎng)問(wèn)this
的屬性,示例:
var msg = "hello world!"; function printMsg() { console.log(this.msg); } printMsg(); // hello world!
小知識(shí):
在對(duì)象中,this
關(guān)鍵字并不指向GEC
,而是指向?qū)ο蟊旧怼?/p>
引用對(duì)象中的this
如同引用:
對(duì)象.定義在對(duì)象內(nèi)部的屬性或方法;
示例代碼:
var msg = "hello world!"; const Obj = { msg = "no hello world!"; printMsg() { console.log(this.msg); } } Obj.printMsg(); // no hello world!
出現(xiàn)上述的情況,函數(shù)可以訪(fǎng)問(wèn)的this
關(guān)鍵字的值是定義其的對(duì)象Obj
,而不是全局對(duì)象。
this
關(guān)鍵字的值設(shè)置后,執(zhí)行上下文對(duì)象的所有屬性就定義完成,創(chuàng)建階段結(jié)束,JS引擎
就進(jìn)入到執(zhí)行階段。
3.2、執(zhí)行階段
執(zhí)行上下文創(chuàng)建階段之后就是執(zhí)行階段了,在這一階段代碼執(zhí)行真正開(kāi)始。創(chuàng)建階段之后,VO
包含的變量值為undefined
,如果在此時(shí)運(yùn)行代碼,肯定會(huì)報(bào)錯(cuò),因此JavaScript
引擎無(wú)法執(zhí)行未定義的變量。
在執(zhí)行階段,JavaScript
引擎會(huì)再次讀取執(zhí)行上下文,并用變量的實(shí)際值更新VO
。編譯器再把代碼編譯為計(jì)算機(jī)可執(zhí)行的字節(jié)碼后執(zhí)行。如果在代碼執(zhí)行過(guò)程中發(fā)生異常,JavaScript
引擎會(huì)拋出異常并停止執(zhí)行代碼。
3.3、銷(xiāo)毀階段
執(zhí)行上下文銷(xiāo)毀階段是指當(dāng)一個(gè)函數(shù)執(zhí)行完畢或者當(dāng)前執(zhí)行上下文被彈出執(zhí)行上下文棧時(shí),執(zhí)行上下文會(huì)被銷(xiāo)毀的過(guò)程。在執(zhí)行上下文銷(xiāo)毀階段,JavaScript引擎
會(huì)執(zhí)行以下步驟:
- 垃圾回收:
JavaScript
引擎會(huì)檢查當(dāng)前執(zhí)行上下文中的變量對(duì)象和函數(shù)聲明是否被其他對(duì)象引用。如果沒(méi)有被引用,則這些對(duì)象將被標(biāo)記為垃圾對(duì)象,并在垃圾回收過(guò)程中被清除。 - 變量銷(xiāo)毀:
JavaScript
引擎會(huì)銷(xiāo)毀當(dāng)前執(zhí)行上下文中的所有變量。在函數(shù)執(zhí)行結(jié)束時(shí),所有局部變量將被銷(xiāo)毀。在全局執(zhí)行上下文中,全局變量只有在頁(yè)面關(guān)閉時(shí)才會(huì)被銷(xiāo)毀。 - 閉包變量銷(xiāo)毀:如果當(dāng)前執(zhí)行上下文是一個(gè)閉包函數(shù),那么其中的閉包變量將不會(huì)被銷(xiāo)毀。這是因?yàn)殚]包變量被外層函數(shù)的作用域鏈所引用,只有當(dāng)外層函數(shù)被銷(xiāo)毀時(shí),閉包變量才會(huì)被銷(xiāo)毀。
- 執(zhí)行上下文彈出:
JavaScript
引擎會(huì)將當(dāng)前執(zhí)行上下文從執(zhí)行上下文棧中彈出,并將控制權(quán)返回給上一個(gè)執(zhí)行上下文。
小知識(shí):
ES5
以上的規(guī)范,對(duì)于執(zhí)行上下文的創(chuàng)建過(guò)程有所調(diào)整,移除了了ES3
中的變量對(duì)象VO
和活動(dòng)對(duì)象AO
,引入了詞法環(huán)境組件(LexicalEnvironment component
) 和變量環(huán)境組件(VariableEnvironment component
)。
4、執(zhí)行棧
執(zhí)行棧又稱(chēng)調(diào)用棧,記錄了腳本整個(gè)生命周期中生成的執(zhí)行上下文。
小知識(shí):
JavaScrip
是單線(xiàn)程語(yǔ)言,也就是說(shuō)它只能在同一時(shí)間執(zhí)行一項(xiàng)任務(wù)。因此,其他的操作、函數(shù)和事件發(fā)生時(shí),執(zhí)行上下文也會(huì)被創(chuàng)建。由于單線(xiàn)程的特性,一個(gè)堆疊了執(zhí)行上下文的棧就會(huì)被創(chuàng)建,稱(chēng)為執(zhí)行棧
。
JS引擎
會(huì)搜索代碼中被調(diào)用的函數(shù)。每一次函數(shù)被調(diào)用,一個(gè)新的FEC
就會(huì)被創(chuàng)建,并被放置在當(dāng)前執(zhí)行上下文的上方。而執(zhí)行棧最頂部的執(zhí)行上下文會(huì)成為活躍執(zhí)行上下文
,并且始終是JS引擎
優(yōu)先執(zhí)行。
一旦活躍執(zhí)行上下文中的代碼被執(zhí)行完畢,JS引擎就會(huì)從執(zhí)行棧中彈出這個(gè)執(zhí)行上下文,緊接著執(zhí)行下一個(gè)執(zhí)行上下文,以此類(lèi)推。
4.1、示例代碼
用一段代碼來(lái)描述執(zhí)行棧的流程
var name = "Guizimo"; function first() { var a = "Hi!"; second(); console.log(`${a} ${name}`); } function second() { var b = "Hey!"; third(); console.log(`$ ${name}`); } function third() { var c = "Hello!"; console.log(`${c} ${name}`); } first();
執(zhí)行結(jié)果:
Hello! Guizimo
Hey! Guizimo
Hi! Guizimo
對(duì)于這個(gè)預(yù)料之中,但總感覺(jué)奇奇怪怪的結(jié)果...所以還是使用圖示來(lái)講解一下。
4.2、圖示講解
JS引擎
加載腳本,創(chuàng)建GEC
,并壓入執(zhí)行棧的最底部。name
變量,first
、second
和third
函數(shù)在所有函數(shù)外部定義,所以位于GEC
,并且被VO
存儲(chǔ)。
當(dāng)JS引擎
遇到first
函數(shù)調(diào)用時(shí),一個(gè)新的FEC
被創(chuàng)建。新的執(zhí)行上下文被放置在當(dāng)前上下文上方,形成執(zhí)行棧
。在first
函數(shù)調(diào)用時(shí),其執(zhí)行上下文變成活躍執(zhí)行上下文。在first
函數(shù)中的變量a ='Hi!'
被存儲(chǔ)在其FEC
中,而非GEC
中。
緊接著,second
函數(shù)在first
函數(shù)中被調(diào)用。由于JavaScript
單線(xiàn)程的特性,first
函數(shù)的執(zhí)行會(huì)被暫停,直到second
函數(shù)執(zhí)行完閉,才會(huì)繼續(xù)執(zhí)行。同樣的,JS引擎
會(huì)給second
函數(shù)設(shè)置一個(gè)新的FEC
,并把它放置在棧頂端,并激活。second
函數(shù)成為活躍執(zhí)行上下文,變量b = 'Hey!'
被存儲(chǔ)在其FEC
中。
再之后second
函數(shù)中的third
函數(shù)被調(diào)用,其FEC
被創(chuàng)建并放置在執(zhí)行棧的頂部。
在third
函數(shù)中的變量c = 'Hello!'
被存儲(chǔ)在其FEC中,Hello! Guizimo
在控制臺(tái)中打印。等待third
函數(shù)執(zhí)行完畢后, 其FEC
就從棧頂端彈出,而調(diào)用third
函數(shù)的second
函數(shù)重新成為活躍執(zhí)行上下文。
回到second
函數(shù),控制臺(tái)打印Hey! Guizimo
。函數(shù)執(zhí)行完成所有任務(wù),這個(gè)執(zhí)行上下文從執(zhí)行棧上彈出。
當(dāng)first
函數(shù)執(zhí)行完畢,從執(zhí)行棧上彈出后,控制流回到代碼的GEC
。
最終,所有代碼執(zhí)行完畢,JS引擎
把GEC
從執(zhí)行棧上彈出。
以上就是詳解JavaScript中的執(zhí)行上下文的詳細(xì)內(nèi)容,更多關(guān)于JavaScript執(zhí)行上下文的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ECharts柱狀排名圖柱子上方顯示文字與圖標(biāo)代碼實(shí)例
我們?cè)诶L制柱狀圖時(shí)如果想要柱條上顯示文字,可以參考本文,這篇文章主要給大家介紹了關(guān)于ECharts柱狀排名圖柱子上方顯示文字與圖標(biāo)的相關(guān)資料,需要的朋友可以參考下2023-11-11微信小程序?qū)崿F(xiàn)動(dòng)態(tài)獲取元素寬高的方法分析
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)動(dòng)態(tài)獲取元素寬高的方法,結(jié)合實(shí)例形式分析了微信小程序動(dòng)態(tài)獲取、設(shè)置元素寬高的相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-12-1220分鐘成功編寫(xiě)bootstrap響應(yīng)式頁(yè)面 就這么簡(jiǎn)單
這篇文章主要教大家如何在20分鐘內(nèi)成功編寫(xiě)bootstrap響應(yīng)式頁(yè)面,其實(shí)很簡(jiǎn)單,培養(yǎng)大家分分鐘開(kāi)發(fā)出一個(gè)高大上的頁(yè)面能力,感興趣的小伙伴們可以參考一下2016-05-05微信小程序使用GoEasy實(shí)現(xiàn)websocket實(shí)時(shí)通訊
這篇文章主要介紹了微信小程序使用GoEasy實(shí)現(xiàn)websocket實(shí)時(shí)通訊的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05微信小程序和H5頁(yè)面間相互跳轉(zhuǎn)代碼實(shí)例
這篇文章主要介紹了微信小程序和H5頁(yè)面間相互跳轉(zhuǎn)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09