序
Event Loop 這個概念相信大家或多或少都瞭解過,但是有一次被一個小夥伴問到它具體的原理的時候,感覺自己只知道個大概印象,於是計劃着寫一篇文章,用輸出倒逼輸入,讓自己重新學習這個概念,同時也能幫助更多的人理解它~
概念
JavaScript 是一門 單線程 語言,即同一時間只能執行一個任務,即代碼執行是同步並且阻塞的。
eg. 這就像只有一個窗口的銀行,客戶需要一個一個排隊辦理業務。
只能同步執行肯定是有問題的,所以 JS 有了一個用來實現異步的函數:setTimeout
下面要講的 Event Loop 就是爲了確保 異步代碼 可以在 同步代碼 執行後繼續執行的。
由於涉及到的相關概念較多,我們先從最簡單的來。
隊列(Queue)
隊列 是一種 FIFO(First In, First Out) 的數據結構,它的特點就是 先進先出
eg. 生活中最常見的例子就是排隊啦,排在隊伍最前面的人最先被提供服務。
棧(Stack)
棧 是一種 LIFO(Last In, First Out)的數據結構,特點即 後進先出。
eg. 大家都吃過桶裝薯片吧~薯片在包裝的時候只能從頂部放入,而吃的時候也只能從頂部拿出,這就叫後進先出哈
調用棧(Call Stack)
棧我們已經知道了,那麼什麼是 調用棧 呢 ?
它本質上當然還是個棧啦 廢話,關鍵在於它裏面裝的東西,是一個個待執行的函數。
Event Loop 會一直檢查 Call Stack 中是否有函數需要執行,如果有,就從棧頂依次執行。同時,如果執行的過程中發現其他函數,繼續入棧然後執行。
先拿兩個函數來說:
- 棧空
- 現在執行到一個 函數A,函數A 入棧
- 函數A 又調用了 函數B,函數B 入棧
- 函數B 執行完後 出棧
- 然後繼續執行 函數A,執行完後A也 出棧
- 棧空
更復雜一點的話,來看一段代碼:
這段代碼在 調用棧中的運行順序如下圖:
這個調用棧其實大家經常會見到,就是在控制檯報錯的時候,錯誤信息顯示的就是當前時刻調用棧的狀態。
But, 上面我們討論的其實都是同步代碼,代碼在運行的時候只用 調用棧 解釋就可以了。
那麼,假如我們發起了一個網絡請求(request),或者設置了一個定時器延時(setTimeout),一段時間後的代碼(回調函數)肯定不是直接被加到調用棧吧?
這時就要引出 事件表格(Event Table) 和 事件隊列 (Event Queue) 了
Event Table
Event Table 可以理解成一張 事件->回調函數
對應表
它就是用來存儲 JavaScript 中的異步事件 (request, setTimeout, IO等) 及其對應的回調函數的列表
Event Queue
Event Queue 簡單理解就是 回調函數 隊列
,所以它也叫 Callback Queue
當 Event Table 中的事件被觸發,事件對應的 回調函數 就會被 push 進這個 Event Queue,然後等待被執行
Event Loop
先來看一個流程圖:
- 開始,任務先進入 Call Stack
- 同步任務直接在棧中等待被執行,異步任務從 Call Stack 移入到 Event Table 註冊
- 當對應的事件觸發(或延遲到指定時間),Event Table 會將事件回調函數移入 Event Queue 等待
- 當 Call Stack 中沒有任務,就從 Event Queue 中拿出一個任務放入 Call Stack
而 Event Loop 指的就是這一整個圈圈:
它不停檢查 Call Stack 中是否有任務(也叫棧幀)需要執行,如果沒有,就檢查 Event Queue,從中彈出一個任務,放入 Call Stack 中,如此往復循環。
好啦,不知道有沒有看明白呢?放一張更經典的圖:
其中與 Event Queue 對應的還有一個叫 Job Queue,它主要是用來執行 Promise 的,這兩種 Queue 有什麼區別呢?
這就涉及到 宏任務 (macro task) 和 微任務 (micro task) 了,我們放在下篇再講~
參考文章
原文鏈接
MDN EventLoop
javascript-event-loop
understanding-js-the-event-loop
這一次,徹底弄懂JavaScript執行機制
understanding-event-loop-call-stack-event-job-queue-in-javascript