本篇文章帶大家來了解Javascript中的執行上下文和執行堆疊、事件循環。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。
下面的這些概念,無論是執行上下文
、 或執行堆疊
,它在規範中的概念都很抽象,很多內容的理解實際靠的都是想像力,若有錯誤之處,還請指正。
執行上下文
簡而言之,執行上下文(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 如果上面的程式碼運行,只會產生兩個執行上下文:<p></p>
的行註解掉,那麼最終只有一個執行上下文,因為函數f
根本就不會執行,自然就不會有對應的執行上下文了。裡面唯一一個迷惑的是,就是for-loop
了,但它根本不是可執行程式碼,所以它是函數執行上下文的一部分
。
一個執行上下文,可以抽象化為:
ExecutionContext = { State: LexEnv = { This: , OuterEnv: , DecRec:{ //... identifiername-variable } } VaEnv = { This: , OuterEnv: , VarRec:{ //... identifiername-variable } } }
事實上,在一個執行上下文中有兩個相當重要的元件:
LexicalEnvironmentComponent(詞法環境元件)和VariableEnvironmentComponent
(變數環境元件)。詞法環境元件指向目前程式碼
中的詞法環境(LexEnv),變數環境元件指向目前程式碼
的變數環境(VarEnv)。 關於
中,有一個很重要的部分就是作用域鏈,但是在執行上下文中並沒有看到相關內容。不過作用域鏈的確存在,它就在[[Scope]]內部屬性中,透過瀏覽器
可以直接看到。 不過也可以這樣理解,當一個執行上下文被創建時,不僅會創建當前詞法環境的
,也會創建LexEnv.OutEnv
、LexEnv.OutEnv.OutEnv
…,直到延伸到全域為止。
1、建立一個新執行上下文(ExecutionContext , EC)
2、建立目前詞法環境(LexEnv和VarEnv)
3、將該執行上下文的
LexicalEnvironmentComponent 和 VariableEnvironmentComponent
指向目前環境下的LexEnv
和VarEnv
#1 #中。
中,並成為運行時執行上下文。 5、對可執行程式碼區塊內的識別碼進行實例化和初始化:#收集目前詞法環境內所有宣告的識別碼歸入
DecRec
中,所有var
聲明的識別碼歸入
偵測,若與let/const/...
宣告的識別碼與VarNames
中的識別符重複, 報錯。 將
DecRec
中的識別符進行實例化,並設為
对于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源代碼,那種頭痛欲裂又渾身顫抖不止的感覺實在是過癮之極啊…
相關教學推薦:JavaScript影片教學
以上是了解JS中的執行上下文、 執行堆疊、事件循環的詳細內容。更多資訊請關注PHP中文網其他相關文章!