詳解JavaScript的執行機制:Event Loop(事件輪詢機制)
前言
有人稱Event Loop為事件迴圈機制,而我更願意將其解釋為事件輪詢機制,在之後的內容中你會感受到這一點的區別在哪裡。說是事件輪詢機制,我們也可以說是任務輪詢機制,因為英文是Event Loop,所以我們在此文中將其翻譯為事件輪詢。
閱讀本文之前,首先對JavaScript的單執行緒和非同步要有一定的瞭解,對此不瞭解的可以先閱讀一下我的另一篇博文《JavaScript的單執行緒和非同步》。
ECMA只負責指定標準,Event Loop如何實現,它並不關心。
本文在概念的結構順序上參考了阮一峰老師的部落格《再談javascript的執行機制: Event Loop》,理解也大多源於此文,加上個人看法,以更容易讓讀者理解的方式表述出來。
在講JavaScript的事件輪詢機制之前,讓我們先來了解幾個重要的概念:
任務佇列
我們知道,由於JavaScript是單執行緒,這意味著所有任務都要排隊等待執行,後面的任務要等待前面的任務執行結束才能開始執行,如果前一個任務耗時比較久的話,後一個任務就必須一直等待。
我們在《JavaScript的單執行緒和非同步》一文中指出,CPU的運算能力往往是過剩的,我們等待的時間主要是IO操作的時間,這時候會發現,有時候javascript的主執行緒完全可以不管這些IO操作的任務,我們可以先將這些任務掛起,執行後面的任務,等到IO操作返回了結果,再將之前掛起的任務繼續執行。
根據上述的情況,我們大致可以將這分為兩種任務:同步任務(synchronous)
捋一下整個流程:
步驟1: 首先解析JavaScript程式碼,這時程式碼未執行,將同步任務放到執行棧中等待被執行;
步驟2: 對執行棧進行判斷,如果執行棧不為空則逐個執行排隊的任務(任務執行過程中產生的非同步任務拋給子執行緒進行處理,當任務結果返回時將一個事件放入任務佇列中等待被讀取);如果執行棧為空,則讀取任務佇列中可執行的事件,將其放到執行棧中等待被執行;
步驟3: 不斷重複步驟2的操作。
注:任務佇列也是一個先進先出的棧,先進入任務佇列的任務會先被讀取到執行棧中等待執行
注意:當執行棧空了,才會去讀取任務佇列,這個過程會不斷重複。 這就是JavaScript的執行機制,沒有我們想象的那麼什麼和複雜,對吧!
事件的概念
“任務佇列"是一個事件的佇列(也可以理解成訊息的佇列),非同步操作完成一項任務,就在"任務佇列"中新增一個事件,表示相關的非同步任務可以進入"執行棧"了。主執行緒讀取"任務佇列”,就是讀取裡面有哪些事件。
“任務佇列"中的事件,還包括滑鼠點選、鍵盤點選,定時操作等等。只要指定過回撥函式,這些事件發生時就會進入"任務佇列”,等待主執行緒讀取。
回撥函式
在講事件輪詢機制之前,我們還要了解一件事情,我們發起非同步任務的目的是什麼?是希望獲得需要的結果,然後根據這個結果去做一些事情對吧,如果非同步任務結果返回了,而我們什麼都不做的話,就失去了發起非同步任務的初衷!我們所謂的主執行緒掛起的任務,實際上是一段待執行程式碼,在非同步結果或者說是狀態返回時,我們執行這段程式碼,也就是執行非同步任務;而這裡的程式碼,我們稱之為回撥函式。
事件輪詢機制Event Loop:
終於講到在任務佇列一章中我們放了一張圖,主執行緒從"任務佇列"中讀取事件,這個過程是迴圈不斷的,故此,我們將其稱為Event Loop(直譯為事件迴圈),照例,先摔一張圖在這裡:
上圖中,主執行緒執行的時候,產生堆(heap)和棧(stack),棧中的程式碼呼叫各種外部API,它們在"任務佇列"中加入各種事件(click,load,error)。只要棧中的程式碼執行完畢,主執行緒就會去讀取"任務佇列",依次執行那些事件所對應的回撥函式。在這裡我們能夠更明確的看出任務佇列是一個先進先出的資料結構。
這就像是每次執行棧為空時,便會詢問任務佇列是否有可執行任務,這也是我文章開頭時為什麼將Event Loop解釋為事件輪詢機制。
注:本文沒有詳細區分“任務佇列“中的情況,之後會詳解“任務佇列”中的不同情況。
結語
此篇主要是講解了JavaScript的執行機制,後面我們會從程式碼層面來深入分析JavaScript在程式碼層面的體現。
希望此文能夠解決大家工作和學習中的一些疑問,避免不必要的時間浪費,有不嚴謹的地方,也請大家批評指正,共同進步!
轉載請註明出處,謝謝!
交流方式:QQ1670765991