
產生原因
為什么會有消息隊列和事件循環呢?首先最關鍵的一點在於JS是個單線程,並且主線程非常繁忙,既要處理 DOM,又要計算樣式,還要處理布局,同時還需要處理 JavaScript 任務以及各種輸入事件。要讓這么多不同類型的任務在主線程中有條不紊地執行,這就需要一個系統來統籌調度這些任務,這個統籌調度系統就是消息隊列和事件循環系統。
消息隊列
作用
消息隊列是一種數據結構,可以存放要執行的任務。它符合隊列“先進先出”的特點,也就是說要添加任務的話,添加到隊列的尾部;要取出任務的話,從隊列頭部去取。
規范⚠️
whatwg標准里,有如下幾個比較重要的地方。
- “An event loop has one or more task queues”。
一個事件循環有一個或者多個任務隊列。
- Task queues are sets not queues, because step one of the event loop processing model grabs the first runnable task from the chosen queue, instead of dequeuing the first task.
任務隊列是set而不是queues,因為事件循環處理模型的第一步從選定的隊列中獲取第一個可運行的任務,而不是使第一個任務出隊。消息隊列其實不算是隊列,因為有很多個任務隊列。每一個任務隊列才是一個隊列,每個任務隊列都是一組任務的集合。而對於每一個任務隊列里的隊列,其任務來源是一致的,或者說不同的任務來源會被推入到不同的任務隊列。
- every task source must be associated with a specific task queue,
即每種任務來源都必須關聯一個任務隊列,任務來源都有DOM操作,UI事件,網絡事件等。都會被放到專門的隊列里。上一輪事件循環結束后,會先選擇一個高優先級的任務隊列,然后取出任務的第一個任務,也因此而有了事件的優先級
whatag規范:https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
注意⚠️: task queue的區分,是表示幾種邏輯上不直接相關聯的task(比如用戶UI操作,網絡請求,DOM更新等),同一類的task執行是不能亂序的。不同類的則可以,這允許瀏覽器給予某種source更高的優先級。
實現
實際上Chrome只實現了一個消息隊列和延遲隊列。雖然WHATWG規范里說每種task source必須關聯一個task queue,但是需要注意的是只有一個task queue所有的task source都關聯到這一個task queue也是符合規范的,這里是留給瀏覽器自行處理的空間
注意⚠️:task queue和task source的關系是一對多,即一個task queue里面可以有多個不同來源的任務
事件循環
作用
在線程運行過程中,接收並執行新的任務,就需要采用事件循環機制。
消息隊列和事件循環的配合♻️
其實事件循環機制和消息隊列的維護是由事件觸發線程控制的。事件觸發線程同樣是瀏覽器渲染引擎提供的,它會維護一個消息隊列。JS引擎線程遇到異步(DOM事件監聽、網絡請求、setTimeout計時器等),會交給相應的線程單獨去維護異步任務,等待某個時機(計時器結束、網絡請求成功、用戶點擊DOM),然后由事件觸發線程將異步對應的回調函數封裝成任務並加入到消息隊列中對應的任務隊列中,等待被執行。
同時,JS引擎線程會維護一個執行棧(調用棧),同步代碼會依次加入執行棧(調用棧)然后執行,結束會退出執行棧。如果執行棧(調用棧)里的任務執行完成,即執行棧為空的時候(即JS引擎線程空閑),事件觸發線程才會從消息隊列取出一個任務(即異步的回調函數)放入執行棧中執行。
執行步驟
- 若有其他進程通信則會通過IPC與IO線程進行消息傳遞,
- IO線程接收到其他進程傳進來的消息后,則會將其添加到消息隊列尾部;
- 渲染主線程循環地從消息隊列頭部中讀取任務,執行任務;
流程圖
注意⚠️:此圖是根據個人理解所畫,畫的是WHATWG規范中的執行流程,在瀏覽器真正的實現中,例如Chrome,他的消息隊列就是單純的消息隊列。里面不含有其他的消息隊列。若理解的有不當之處還望指出。
存在的問題
如何處理高優先級任務?
添加了微任務隊列來解決,通常我們把消息隊列中的任務稱為宏任務,每個宏任務中都包含了一個微任務隊列,在執行宏任務的過程中,如果DOM有變化,那么就會將該變化添加到微任務列表中,這樣就不會影響到宏任務的繼續執行,因此也就解決了執行效率的問題。等宏任務中的主要功能都直接完成之后,這時候,渲染引擎並不着急去執行下一個宏任務,而是執行當前宏任務中的微任務,因為 DOM 變化的事件都保存在這些微任務隊列中,這樣也就解決了實時性問題。
如何解決單個任務執行時長過久的問題?
JavaScript 可以通過回調功能來規避這種問題,也就是讓要執行的 JavaScript 任務滯后執行。
參考
瀏覽器工作原理與實踐
whatwg規范
特別感謝🙏
感謝賀師俊賀老在知乎對我提問的關於消息隊列問題的解惑🙏🙏🙏,回答鏈接👇