Event Loop 是 JavaScript 的基礎概念,面試必問,平時也經常談到,但是有沒有想過為什麼會有 Event Loop,它為什麼會這樣設計的呢?
今天我們就來探索下原因。
JavaScript 是用於實現網頁互動邏輯的,涉及到 dom 操作,如果多個執行緒同時操作需要做同步互斥的處理,為了簡化就設計成了單執行緒,但是如果單執行緒的話,遇到定時邏輯、網路請求又會阻塞住。怎麼辦呢?
可以加一層排程邏輯。把 JS 程式碼封裝成一個個的任務,放在一個任務佇列中,主執行緒就不斷的取任務執行就好了。
每次取任務執行,都會建立新的呼叫棧。
其中,定時器、網路請求其實都是在別的執行緒執行的,執行完了之後在任務佇列裡放個任務,告訴主執行緒可以繼續往下執行了。
因為這些非同步任務是在別的執行緒執行完,然後通過任務佇列通知下主執行緒,是一種事件機制,所以這個迴圈叫做 Event Loop。
這些在其他執行緒執行的非同步任務包括定時器(setTimeout、setInterval),UI 渲染、網路請求(XHR 或 fetch)。
但是,現在的 Event Loop 有個嚴重的問題,沒有優先順序的概念,只是按照先後順序來執行,那如果有高優先順序的任務就得不到及時的執行了。所以,得設計一套插隊機制。
那就搞一個高優先順序的任務佇列就好了,每執行完一個普通任務,都去把所有高優先順序的任務給執行完,之後再去執行普通任務。
有了插隊機制之後,高優任務就能得到及時的執行。
這就是現在瀏覽器的 Event Loop。
其中普通任務叫做 MacroTask(宏任務),高優任務叫做 MicroTask(微任務)。
宏任務包括:setTimeout、setInterval、requestAnimationFrame、Ajax、fetch、script 標籤的程式碼。
微任務包括:Promise.then、MutationObserver、Object.observe。
怎麼理解宏微任務的劃分呢?
定時器、網路請求這種都是在別的執行緒跑完之後通知主執行緒的普通非同步邏輯,所以都是宏任務。
而高優任務的這三種也很好理解,MutationObserver 和 Object.observe 都是監聽某個物件的變化的,變化是很瞬時的事情,肯定要馬上響應,不然可能又變了,Promise 是組織非同步流程的,非同步結束呼叫 then 也是很高優的。
這就是瀏覽器裡的 Event Loop 的設計:設計 Loop 機制和 Task 佇列是為了支援非同步,解決邏輯執行阻塞主執行緒的問題,設計 MicroTask 佇列的插隊機制是為了解決高優任務儘早執行的問題。
但是後來,JS 的執行環境不只是瀏覽器一種了,還有了 Node.js,它同樣也要解決這些問題,但是它設計出來的 Event Loop 更細緻一些。
是一個新的 JS 執行環境,它同樣要支援非同步邏輯,包括定時器、IO、網路請求,很明顯,也可以用 Event Loop 那一套來跑。
但是呢,瀏覽器那套 Event Loop 就是為瀏覽器設計的,對於做高效能伺服器來說,那種設計還是有點粗糙了。
哪裡粗糙呢?
瀏覽器的 Event Loop 只分了兩層優先順序,一層是宏任務,一層是微任務。但是宏任務之間沒有再劃分優先順序,微任務之間也沒有再劃分優先順序。
而 Node.js 任務宏任務之間也是有優先順序的,比如定時器 Timer 的邏輯就比 IO 的邏輯優先順序高,因為涉及到時間,越早越準確;而 close 資源的處理邏輯優先順序就很低,因為不 close 最多多佔點記憶體等資源,影響不大。
於是就把宏任務佇列拆成了五個優先順序:Timers、Pending、Poll、Check、Close。
解釋一下這五種宏任務:
Timers Callback: 涉及到時間,肯定越早執行越準確,所以這個優先順序最高很容易理解。
Pending Callback:處理網路、IO 等異常時的回撥,有的 *niux 系統會等待發生錯誤的上報,所以得處理下。
Poll Callback:處理 IO 的 data,網路的 connection,伺服器主要處理的就是這個。
Check Callback:執行 setImmediate 的回撥,特點是剛執行完 IO 之後就能回撥這個。
Close Callback:關閉資源的回撥,晚點執行影響也不到,優先順序最低。
所以呢,Node.js 的 Event Loop 就是這樣跑的了:
還有一點不同要特別注意:
Node.js 的 Event Loop 並不是瀏覽器那種一次執行一個宏任務,然後執行所有的微任務,而是執行完一定數量的 Timers 宏任務,再去執行所有微任務,然後再執行一定數量的 Pending 的宏任務,然後再去執行所有微任務,剩餘的 Poll、Check、Close 的宏任務也是這樣。(訂正:node 11 之前是這樣,node 11 之後改為了每個宏任務都執行所有微任務了)
為什麼這樣呢?
其實按照優先順序來看很容易理解:
假設瀏覽器裡面的宏任務優先順序是 1,所以是按照先後順序依次執行,也就是一個宏任務,所有的微任務,再一個宏任務,再所有的微任務。
而 Node.js 的 宏任務之間也是有優先順序的,所以 Node.js 的 Event Loop 每次都是把當前優先順序的所有宏任務跑完再去跑微任務,然後再跑下一個優先順序的宏任務。
也就是是一定數量的 Timers 宏任務,再所有微任務,再一定數量的 Pending Callback 宏任務,再所有微任務這樣。
為什麼說是一定數量呢?
因為如果某個階段宏任務太多,下個階段就一直執行不到了,所以有個上限的限制,剩餘的下個 Event Loop 再繼續執行。
除了宏任務有優先順序,微任務也劃分了優先順序,多了一個 process.nextTick 的高優先順序微任務,在所有的普通微任務之前來跑。
所以,Node.js 的 Event Loop 的完整流程就是這樣的:
比起瀏覽器裡的 Event Loop,明顯複雜了很多,但是經過我們之前的分析,也能夠理解:
Node.js 對宏任務做了優先順序劃分,從高到低分別是 Timers、Pending、Poll、Check、Close 這 5 種,也對微任務做了劃分,也就是 nextTick 的微任務和其他微任務。執行流程是先執行完當前優先順序的一定數量的宏任務(剩下的留到下次迴圈),然後執行 process.nextTick 的微任務,再執行普通微任務,之後再執行下個優先順序的一定數量的宏任務。。這樣不斷迴圈。其中還有一個 Idle/Prepare 階段是給 Node.js 內部邏輯用的,不需要關心。
改變了瀏覽器 Event Loop 裡那種一次執行一個宏任務的方式,可以讓高優先順序的宏任務更早的得到執行,但是也設定了個上限,避免下個階段一直得不到執行。
還有一個特別要注意的點,就是 poll 階段:如果執行到 poll 階段,發現 poll 佇列為空並且 timers 佇列、check 佇列都沒有任務要執行,那麼就阻塞的等在這裡等 IO 事件,而不是空轉。 這點設計也是因為伺服器主要是處理 IO 的,阻塞在這裡可以更早的響應 IO。
完整的 Node.js 的 Event Loop 是這樣的:
對比下瀏覽器的 Event Loop:
兩個 JS 執行環境的 Event Loop 整體設計思路是差不多的,只不過 Node.js 的 Event Loop 對宏任務和微任務做了更細粒度的劃分,也很容易理解,畢竟 Node.js 面向的環境和瀏覽器不同,更重要的是伺服器端對效能的要求會更高。
JavaScript 最早是用於寫網頁互動邏輯的,為了避免多執行緒同時修改 dom 的同步問題,設計成了單執行緒,又為了解決單執行緒的阻塞問題,加了一層排程邏輯,也就是 Loop 迴圈和 Task 佇列,把阻塞的邏輯放到其他執行緒跑,從而支援了非同步。然後為了支援高優先順序的任務排程,又引入了微任務佇列,這就是瀏覽器的 Event Loop 機制:每次執行一個宏任務,然後執行所有微任務。
Node.js 也是一個 JS 執行環境,想支援非同步同樣要用 Event Loop,只不過伺服器端環境更復雜,對效能要求更高,所以 Node.js 對宏微任務都做了更細粒度的優先順序劃分:
Node.js 裡劃分了 5 種宏任務,分別是 Timers、Pending、Poll、Check、Close。又劃分了 2 種微任務,分別是 process.nextTick 的微任務和其他的微任務。
Node.js 的 Event Loop 流程是執行當前階段的一定數量的宏任務(剩餘的到下個迴圈執行),然後執行所有微任務,一共有 Timers、Pending、Idle/Prepare、Poll、Check、Close 6 個階段。(訂正:node 11 之前是這樣,node 11 之後改為了每個宏任務都執行所有微任務了)
其中 Idle/Prepare 階段是 Node.js 內部用的,不用關心。
特別要注意的是 Poll 階段,如果執行到這裡,poll 佇列為空並且 timers、check 佇列也為空,就一直阻塞在這裡等待 IO,直到 timers、check 佇列有回撥再繼續 loop。
Event Loop 是 JS 為了支援非同步和任務優先順序而設計的一套排程邏輯,針對瀏覽器、Node.js 等不同環境有不同的設計(主要是任務優先順序的劃分粒度不同),Node.js 面對的環境更復雜、對效能要求更高,所以 Event Loop 設計的更復雜一些。
【相關推薦:、 】
以上就是探索下瀏覽器和 Node.js 為什麼會這樣設計 EventLoop!的詳細內容,更多請關注TW511.COM其它相關文章!