您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)什么是JS中的執(zhí)行上下文、 執(zhí)行棧、事件循環(huán),小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。
下面的這些概念,無(wú)論是執(zhí)行上下文
、 還是執(zhí)行棧
,它在規(guī)范中的概念都很抽象,很多內(nèi)容的理解實(shí)際靠的都是想象力,若有錯(cuò)誤之處,還請(qǐng)指正。
執(zhí)行上下文
簡(jiǎn)而言之,執(zhí)行上下文(Execution Context)是正在運(yùn)行的可執(zhí)行代碼所處環(huán)境的抽象,用于追蹤一段代碼中變量的求值。這是我總結(jié)過(guò)來(lái)的概念,可能有些不準(zhǔn)確,也可以參考真正的規(guī)范定義。
不過(guò)總的來(lái)說(shuō),有三個(gè)關(guān)鍵點(diǎn):
只有可執(zhí)行代碼才會(huì)有執(zhí)行上下文
執(zhí)行上下文是有狀態(tài)的:運(yùn)行狀態(tài)(Perform)、掛起狀態(tài)(Suspend)以及恢復(fù)(Resume)。處于Perfrom狀態(tài)的執(zhí)行上下文稱(chēng)之為運(yùn)行時(shí)執(zhí)行上下文(Running Execution Context)
執(zhí)行上下文完全不等價(jià)于詞法環(huán)境,硬說(shuō)關(guān)系,也只是前者引用了后者而已。
執(zhí)行一個(gè)JS腳本時(shí),可以有多個(gè)執(zhí)行上下文存在,但是 運(yùn)行時(shí)上下文只有唯一一個(gè)(異步也是如此,至于為什么提了四個(gè)……三大天王有四個(gè)不是常識(shí)么……)。
并且ES規(guī)范中規(guī)定,可執(zhí)行代碼有下面幾個(gè):
全局代碼
函數(shù)代碼
Eval語(yǔ)句
模塊代碼
換言之,看以下代碼:
var g=111 function f(){ console.log(g); for(let i =0; i < 10; i++){ console.log(i); } } f(); /// (*)
如果上面的代碼運(yùn)行,只會(huì)產(chǎn)生兩個(gè)執(zhí)行上下文:
全局
函數(shù)f
但是如果將標(biāo)注(*)
的行注釋掉,那么最終只有一個(gè)執(zhí)行上下文,因?yàn)楹瘮?shù)f
根本就不會(huì)執(zhí)行,自然就不會(huì)有相應(yīng)的執(zhí)行上下文了。里面唯一一個(gè)迷惑的是,就是for-loop
了,但它根本不是可執(zhí)行代碼,所以它是函數(shù)執(zhí)行上下文的一部分。
執(zhí)行上下文的重要組成部分
一個(gè)執(zhí)行上下文,可以抽象為:
ExecutionContext = { State: <> LexEnv = { This:< ... > , OuterEnv:< ... > , DecRec:{ //... identifiername-variable } } VaEnv = { This:< ... > , OuterEnv:< ... > , VarRec:{ //... identifiername-variable } } }
事實(shí)上,在一個(gè)執(zhí)行上下文中有兩個(gè)相當(dāng)重要的組件:LexicalEnvironmentComponent
(詞法環(huán)境組件)和VariableEnvironmentComponent
(變量環(huán)境組件)。詞法環(huán)境組件指向當(dāng)前代碼中的詞法環(huán)境(LexEnv
), 變量環(huán)境組件指向當(dāng)前代碼的變量環(huán)境(VarEnv
)。
關(guān)于執(zhí)行上下文不得不說(shuō)的二三事中,有一個(gè)很重要的部分就是作用域鏈,但是在執(zhí)行上下文中并沒(méi)有看到相關(guān)內(nèi)容。不過(guò)作用域鏈的確存在,它就在[[Scope]]
內(nèi)部屬性中,通過(guò)瀏覽器可以直接看到。
不過(guò)也可以這樣理解,在一個(gè)執(zhí)行上下文被創(chuàng)建時(shí),不僅會(huì)創(chuàng)建當(dāng)前詞法環(huán)境的LexEnv
,也會(huì)創(chuàng)建LexEnv.OutEnv
、LexEnv.OutEnv.OutEnv
…,直至延伸到全局為止。
執(zhí)行上下文的創(chuàng)建與銷(xiāo)毀
1、創(chuàng)建一個(gè)新執(zhí)行上下文(ExecutionContext , EC)
2、創(chuàng)建當(dāng)前詞法環(huán)境(LexEnv 和 VarEnv )
3、將該執(zhí)行上下文的 LexicalEnvironmentComponent
和 VariableEnvironmentComponent
指向當(dāng)前環(huán)境下的 LexEnv
和 VarEnv
中。
4、將新執(zhí)行上下文推入 執(zhí)行棧
中,并成為運(yùn)行時(shí)執(zhí)行上下文。
5、對(duì)可執(zhí)行代碼塊內(nèi)的標(biāo)識(shí)符進(jìn)行實(shí)例化和初始化:
收集當(dāng)前詞法環(huán)境內(nèi)所有聲明的標(biāo)識(shí)符歸入DecRec中,所有var
聲明的標(biāo)識(shí)符歸入VarNames
集合中,在此階段會(huì)進(jìn)行標(biāo)識(shí)符名檢測(cè),若與let/const/...
聲明的標(biāo)識(shí)符與VarNames
中的標(biāo)識(shí)符重復(fù), 報(bào)錯(cuò)。
將DecRec
中的標(biāo)識(shí)符進(jìn)行實(shí)例化,并設(shè)為uninitialized
。VarNames
中的標(biāo)識(shí)符綁定在ObjRec
中,實(shí)例化后直接初始化為undefined
。
對(duì)于function
聲明的函數(shù),將直接指向函數(shù)對(duì)象,并也會(huì)綁定到ObjRec中,這是瀏覽器默認(rèn)行為。
6、運(yùn)行代碼。
非var聲明的標(biāo)識(shí)符會(huì)在聲明處進(jìn)行初始化(默認(rèn)為undefined
)。
完成所有變量的賦值,并可能會(huì)一直在變化。
7、運(yùn)行完畢從 執(zhí)行棧
中彈出。
備注:
This
綁定,大部分情況可以用過(guò)去的說(shuō)法解釋?zhuān)欢承┣闆r下卻不盡然。執(zhí)行棧與事件循環(huán)
執(zhí)行棧(Execution Stack)就是由執(zhí)行上下文構(gòu)成的堆棧,類(lèi)似于Call Stack
。
1、當(dāng)Javascript引擎
遇到一段可執(zhí)行代碼時(shí),新建一個(gè)執(zhí)行上下文。
2、將它推入執(zhí)行棧中。并設(shè)置為運(yùn)行時(shí)執(zhí)行上下文。
3、執(zhí)行上下文運(yùn)行完畢,彈出銷(xiāo)毀恢復(fù)并將原執(zhí)行上下文設(shè)為運(yùn)行時(shí)。
總覺(jué)得這些沒(méi)什么好說(shuō)的,但是水一下吧
執(zhí)行棧最重要的部分并非是執(zhí)行棧概念本身,而是與任務(wù)隊(duì)列的關(guān)系,它是事件循環(huán)的入門(mén)關(guān)鍵概念之一。
眾所周知,Javascript語(yǔ)言是單線程的,此處的執(zhí)行棧就相當(dāng)于主線程的調(diào)用棧,也是唯一一個(gè)調(diào)用棧,至于什么是主線程可以查閱相關(guān)資料,這里有些超綱了……
那么javascript是如何實(shí)現(xiàn)異步的?
確切來(lái)說(shuō),這不是Javascript核心的部分,它是結(jié)合瀏覽器API(如Web Worker, Browser-context了解一下)實(shí)現(xiàn)的。
在事件循環(huán)中(事件處理過(guò)程),有兩個(gè)極其重要的概念:
這兩個(gè)概念,是抽象滴。
在Javascript中,一個(gè)任務(wù)也可以稱(chēng)之為事件,通常是一個(gè)函數(shù)回調(diào),由許多任務(wù)組成的隊(duì)列,就是所謂的任務(wù)序列了。任務(wù)序列有很多分類(lèi),例如:作業(yè)序列(Job Quenue)、消息序列(Message Quenue),本質(zhì)沒(méi)區(qū)別。
不必再深入了解,現(xiàn)在需要記住的是:一個(gè)任務(wù)序列中的任務(wù)如果想要被執(zhí)行,就必須將它取出放入執(zhí)行棧中。
例如下面的代碼:
var temp = 10; console.log('push task1'); setTimeout(function task1(){ temp+=10; console.log(temp+'task1 okay! '); },1000) console.log('taskquenue=[task1]; push task2'); setTimeout(function task2(){ temp*=10; console.log(temp+'task2 okay! '); },500) console.log('taskquenue=[task1,task2]; push task3'); setTimeout(function task3(){ temp*= -0.2; console.log(temp+'task3 okay! '); },1500) console.log('taskquenue=[task1, task2,task3]');
輸出如下:
push task1 taskquenue=[task1]; push task2 taskquenue=[task1,task2]; push task3 taskquenue=[task1, task2,task3] 100task2 okay! 110task1 okay! -22task3 okay!
setTimeout
是一個(gè)定時(shí)器,它能夠?qū)⑷蝿?wù)放到任務(wù)隊(duì)列中。如圖:
task1
:task2
:task3
:執(zhí)行到此處, task1
、task2
和task3
都被放入了任務(wù)隊(duì)列; 然后執(zhí)行棧全部執(zhí)行完畢后,開(kāi)始處理任務(wù)隊(duì)列中的任務(wù)。
為什么任務(wù)隊(duì)列中的任務(wù)必須在執(zhí)行???/strong>時(shí)后執(zhí)行呢?
C++
、Java
這類(lèi)多線程的語(yǔ)言或是看看操作系統(tǒng)這方面的內(nèi)容。現(xiàn)在開(kāi)始處理任務(wù)吧:
task2
:task1
:task3
:好了,一個(gè)事件循環(huán)就這么結(jié)束了。
然后Javascript引擎進(jìn)入休眠階段(Javascript引擎永不結(jié)束!),等待有新的任務(wù)執(zhí)行,然后開(kāi)始下一個(gè)事件循環(huán)。
這是我精讀Javascript系列第三篇,猝不及防的就到了事件循環(huán),看起來(lái)一下子就深入好多好多…… 但是我覺(jué)得這才是最不合理程安排,大多的文檔都把任務(wù)序列和調(diào)用棧給分開(kāi)了,但是在~~_____~~,它們本應(yīng)該就是一體,不應(yīng)該以任何方便的理由將它們分開(kāi)。
深入Javascript, 除了看規(guī)范,最好也看下JS引擎的實(shí)現(xiàn)文檔,有些進(jìn)階內(nèi)容,不在規(guī)范中,而是在這些文檔里(請(qǐng)自己Google查找,百度也能搜索到)。
如果對(duì)Javascript引擎比較感興趣的,可以參考:
雖然,極其不建議新手一下子看V8源代碼,那種頭痛欲裂又渾身顫抖不止的感覺(jué)實(shí)在是過(guò)癮之極啊……
以上就是什么是JS中的執(zhí)行上下文、 執(zhí)行棧、事件循環(huán),小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。