Node.js 和瀏覽器的事件迴圈雖然都是基於事件驅動的架構,但它們在實現和一些細節上有所不同。主要區別如下:
1. I/O 處理:
- 瀏覽器: 瀏覽器中的 I/O 操作主要依賴於 Web APIs(例如:
fetch
,XMLHttpRequest
,setTimeout
等)。瀏覽器負責管理這些 API,並在操作完成後將相應的事件新增到事件佇列中。 - Node.js: Node.js 基於 libuv 庫來處理 I/O 操作。libuv 提供了一個執行緒池來處理耗時的 I/O 操作,並在操作完成後將事件新增到事件佇列中。 這使得 Node.js 能夠高效地處理併發 I/O。
2. 事件迴圈的階段:
兩者都遵循類似的事件迴圈階段概念,但階段的名稱和具體功能略有不同。
-
瀏覽器 (HTML5 規範):
- Task Queue (Macrotask 佇列): 處理
setTimeout
,setInterval
,setImmediate
(IE 特有),requestAnimationFrame
, I/O, UI rendering 等。一個事件迴圈 tick 只會處理一個 macrotask。 - Microtask Queue: 處理
Promise
的then
、catch
、finally
回撥,queueMicrotask
等。在當前 macrotask 執行完畢後,會立即執行所有 microtask,直到 microtask 佇列為空。 這意味著在一個事件迴圈 tick 中,可能會有多個 microtask 執行。
- Task Queue (Macrotask 佇列): 處理
-
Node.js (libuv): Node.js 的事件迴圈更復雜,包含更多階段:
- timers: 處理
setTimeout
和setInterval
的回撥。 - pending callbacks: 處理一些系統操作的回撥,例如 TCP 錯誤。
- idle, prepare: 僅供 libuv 內部使用。
- poll: 檢索新的 I/O 事件;執行與 I/O 相關的回撥(幾乎所有回撥都在此階段執行,除了 close callbacks, timers 和 setImmediate)。
- check: 執行
setImmediate()
的回撥。 - close callbacks: 處理一些關閉的回撥,例如
socket.on('close', ...)
。
- timers: 處理
3. setImmediate()
vs. setTimeout(..., 0)
:
- 瀏覽器:
setImmediate
不是標準的 Web API,只有 IE 支援。setTimeout(..., 0)
的實際延遲時間通常受瀏覽器最小延遲時間的限制 (通常是 4ms)。 - Node.js:
setImmediate
和setTimeout(..., 0)
都用於將回撥函式放到下一個事件迴圈 tick 中執行。但在 Node.js 中,setImmediate
的回撥通常在poll
階段之後、check
階段執行,而setTimeout(..., 0)
的回撥在timers
階段執行。setImmediate
通常比setTimeout(..., 0)
執行得更早,尤其是在 I/O 操作較多的情況下。
4. 環境:
- 瀏覽器: 執行在瀏覽器環境中,提供 DOM API,與使用者介面互動。
- Node.js: 執行在伺服器端環境,提供檔案系統、網路等伺服器端 API。
總結:
儘管兩者都使用事件迴圈來處理非同步操作,但由於執行環境和底層實現的不同,它們在 I/O 處理、事件迴圈階段、定時器行為等方面存在差異。理解這些差異對於編寫高效的瀏覽器和 Node.js 應用程式至關重要。