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