tags: Event Loop Frontend JavaScript 非同步 創建時間:2023年12月05日
深入理解 Event loop
什麼是 Event Loop? #
Event loop 是瀏覽器(JavaScript 宿主環境)調度 JavaScript 任務執行的機制,任務類型包含任務、微任務。
那至於為何需要這個調度機制?我們可以先認識 JavaScript 單執行緒非同步的概念,單執行緒代表 JavaScript 在同個時間點只能執行單個任務,非同步則代表當程式碼執行時遇到耗時的 Web API 操作(例如網路 API 請求、讀取文件等)程式碼不會被阻塞,可以繼續往下執行,耗時的操作會透過瀏覽器在背後執行完畢後,接著通過一套機制將非同步執行完畢對應的處理任務排隊,等待主執行緒執行完前面的任務後接續執行。
而其中這個調度任務的機制,就是 Event loop,透過 Event loop,JavaScript 這門單執行緒非同步的語言在遇到多個非同步 Web API 操作結束,接續對應任務要執行時,也能順暢調度使主 JavaScript 執行緒執行所有任務。
舉例來說,我們平常呼叫後端 API(使用 Fetch Web API)時,瀏覽器仍可執行其他操作,當網路請求完成後,若是有對應的任務要執行,背後會有 Event loop 的調度,讓對應任務可以被排入主執行緒執行。
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
// 執行網路請求時,主執行緒仍可運行
// 網路請求完成,對應的 .then 內 callback 任務則會被 Event loop 排入主執行緒開始執行
什麼是執行緒?
執行緒是執行程式碼的最小單位,依照程式語言的不同,分為單執行緒、多執行緒。
什麼是單執行緒、多執行緒?
單線程等於單執行緒,指的是只有一個執行緒,程式碼由單一執行緒執行。多線程等於多執行緒,指的是具備多個執行緒,多個執行緒會同時執行程式碼的不同部分。
在 JavaScript 中,宿主環境指的是什麼?
宿主環境 host environment 指的是執行 JavaScript 的環境,例如:瀏覽器、Node.js。
什麼是同步、非同步?
同步、非同步指的是執行任務的不同方式。
- 同步指的是任務會一個一個執行。
- 非同步在 JavaScript 是指調用 Web API 執行非同步操作時,當前程式不會被非同步操作阻塞,可以繼續執行。
深入 Event loop 執行機制 #
剛剛有說到 Event loop 是瀏覽器調度 JavaScript 任務執行的機制,在介紹調度機制前,我們需要先認識任務,任務類型包含任務、微任務兩種,而在瀏覽器中主要會有哪些任務?我們可以從認識 Web 網頁應用生命週期來理解什麼是任務。
Web 網頁應用生命週期 #
圖片出自 忍者 JavaScript 開發技巧探秘 第二版
Web 網頁應用生命週期主要分為兩階段:
- 階段一:HTML 解析,加載頁面階段
- 階段二:持續監聽事件,並調動事件任務執行階段
在階段一,瀏覽器首先會解析 HTML 並執行,此時 <script>
內的全域程式碼也會在解析時被執行,當頁面加載執行完畢,則階段二開始,瀏覽器會開始監聽各種用戶、瀏覽器事件,並且執行對應事件處理,一旦有用戶、瀏覽器行為觸發,且行為有註冊對應的事件處理函式,處理函式會作為任務等待 Event loop 將其安排進主執行緒執行。
所以我們可以得知,任務主要由各種事件引發,主要有以下幾種:
setTimeout
執行完等待調度的函式- 網路請求完畢後等待執行的函式
- JavaScript 的全域程式(非由事件引發)
- 用戶、瀏覽器觸發事件的事件處理器函式
了解完任務類型,接著便可以來了解 Event loop 執行的機制。
Event loop 執行機制 #
以下為 Event loop 執行機制示意圖:
圖片出自 https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif
Event loop 調度機制會監聽主執行緒,當主執行緒內沒有任何執行中的任務時,會將 task queue 中的任務採先進先出 FIFO 的方式推入主執行緒,一次一個,等到主執行緒內再度無任務時,繼續將 task queue 中的任務採 FIFO 推入。
範例圖中名詞說明:
- call stack - 紀錄主執行緒函式執行呼叫的資料結構
- Web API - 瀏覽器提供的 Web API,例如
setTimeout
、Fetch
- queue - 也稱作 task queue,等待執行的任務會存放在這裡
我們可以看一個實際的例子來了解 Event loop 機制:
setTimeout(() => {
console.log('timeout 1')
}, 0)
foo()
setTimeout(() => {
console.log('timeout 2')
}, 0)
function foo(){
console.log('foo')
}
以上結果為
foo
timeout1
timeout2
依照 Event loop 調度,程式碼執行順序為:
- 全域程式碼在主執行緒開始執行
- 執行第一個 Web API
setTimeout
,倒數時間設置為 0 的 callback 任務 1 立即被 Web API 推送入 task queue - 執行
foo
,foo 被打印在 console - 執行第二個 Web API
setTimeout
,倒數時間設置為 0 的 callback 任務 2 立即被 Web API 推送入 task queue
- 執行第一個 Web API
- 全域程式碼執行完畢
- Event loop 確認當前主執行緒中無任務,將第一個位於 task queue 的
callback1
推入主執行緒執行 callback1
執行完畢- Event loop 確認當前主執行緒中無任務,將第一個位於 task queue 的
callback2
推入主執行緒執行 callback2
執行完畢- Event loop 持續監聽主執行緒是否為空,以及 task queue 是否有任務等待調度被推入主執行緒
setTimeout 何時開始倒數?
setTimeout 函式在被呼叫後,會立即被放入 Web API 中進行倒數,而不需等待全域程式碼的執行完畢。
setTimeout 在哪裡倒數?
會透過 Web API 在瀏覽器背後進行倒數,倒數結束後會被 Web API 推入 task queue。
如果今天是發生事件點擊,事件處理器是如何被推入 task queue?
當事件發生並被監聽器偵測到時,對應的事件處理器會被推入 task queue 中,等待 Event loop 調度執行。
什麼是微任務? #
剛剛提到的都是任務,不過 Event loop 中除了調度任務執行,也包含對於微任務的調度,那微任務是什麼?
微任務主要包含:
- Promise
- 畫面渲染任務
添加上微任務的 Event loop 執行概念圖如下:
圖片出自 忍者 JavaScript 開發技巧探秘
每執行一次任務,會是一個 Event loop 迴圈,在每一個迴圈執行時,只會有一個任務,不過所有被排入 microtask queue 的微任務都會被執行完畢。
舉例來說:
setTimeout(() => {
console.log('settimeout1')
Promise.resolve().then(() => {
console.log('promise then callback1')
}).then(() => {
console.log('promise then callback1-1')
}).then(() => {
console.log('promise then callback1-2')
})
}, 0)
setTimeout(() => {
console.log('settimeout2')
Promise.resolve().then(() => {
console.log('promise then callback2')
})
}, 0)
以上結果為
settimeout1
promise then callback1
promise then callback1-1
promise then callback1-2
settimeout1
promise then callback2
依照 Event loop 調度,程式碼執行順序為:
- 全域程式碼在主執行緒開始執行
- 執行第一個 Web API
setTimeout
,倒數時間設置為 0 的 callback1 任務立即被 Web API 推送入 task queue - 執行第二個 Web API
setTimeout
,倒數時間設置為 0 的 callback2 任務立即被 Web API 推送入 task queue
- 執行第一個 Web API
- 全域程式碼執行完畢
- Event loop 確認當前主執行緒中無任務,將第一個位於 task queue 的
callback1
推入主執行緒執行- 打印 settimeout1
- Promise 完成,微任務 then 皆被推入 microtask queue,開始依序執行打印 promise then callback1、promise then callback1-1、promise then callback1-1
- Event loop 確認當前主執行緒中無任務,將第二個位於 task queue 的
callback2
推入主執行緒執行- 打印 settimeout2
結語 #
透過本文的探討,我們深入了解了 JavaScript 中重要的 Event Loop 概念,這是瀏覽器調度 JavaScript 任務執行的機制。單執行緒非同步的特性讓 JavaScript 具有高度的靈活性,能夠處理多個非同步操作而不阻塞主執行緒。也了解 Event Loop 如何調度不同類型的任務,包括定時器、網路請求、全域程式碼和事件處理函式。有任何問題也歡迎和筆者討論交流歐 ʕ•ᴥ•ʔ。
參考資料 #
-
忍者:JavaScript 開發技巧探秘 第二版
-
The Node.js Event Loop, Timers, and process.nextTick() | Node.js
-
所以說Event loop到底是什麼玩意兒?| Philip Roberts | JSConf EU - YouTube
tags: Event Loop Frontend JavaScript 非同步 創建時間:2023年12月05日