js事件循環機制和ui渲染
事件循環
任務隊列
所有的任務可以分爲同步任務和異步任務,同步任務,顧名思義,就是立即執行的任務,同步任務一般會直接進入到主線程中執行;而異步任務,就是異步執行的任務,比如ajax網絡請求,setTimeout 定時函數等都屬於異步任務,異步任務會通過任務隊列( Event Queue )的機制來進行協調。
同步和異步任務分別進入不同的執行環境,同步的進入主線程,即主執行棧,異步的進入 Event Queue
。主線程內的任務執行完畢爲空,會去 Event Queue 讀取對應的任務,推入主線程執行。 上述過程的不斷重複就是我們說的 Event Loop
(事件循環)。
在事件循環中,每進行一次循環操作稱爲tick
,通過閱讀規範可知,每一次 tick 的任務處理模型是比較複雜的,其關鍵的步驟可以總結如下:
- 在此次 tick 中選擇最先進入隊列的任務( oldest task ),如果有則執行(一次)
- 檢查是否存在 Microtasks ,如果存在則不停地執行,直至清空Microtask Queue
- 更新 render
- 主線程重複執行上述步驟
可以用一張圖來說明下流程:
那麼,什麼是 microtasks ?規範中規定,task分爲兩大類, 分別是 Macro Task (宏任務)和 Micro Task(微任務), 並且每個宏任務結束後, 都要清空所有的微任務,這裏的 Macro Task也是我們常說的 task 。
(macro)task
主要包含:script( 整體代碼)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 環境)
microtask
主要包含:Promise、MutaionObserver、process.nextTick(Node.js 環境)
示例:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
- 整體 script 作爲第一個宏任務進入主線程,遇到 console.log,輸出 script start
- 遇到 setTimeout,其回調函數被分發到
宏任務 Event Queue
中 - 遇到 Promise,其 then函數被分到到
微任務 Event Queue
中,記爲 then1,之後又遇到了 then 函數,將其分到微任務 Event Queue 中,記爲 then2 - 遇到 console.log,輸出 script end
至此,Event Queue 中存在三個任務,如下表:
宏任務 | 微任務 |
---|---|
setTimeout | then1 |
- | then2 |
- 執行微任務,首先執行then1,輸出 promise1, 然後執行 then2,輸出 promise2,這樣就清空了所有微任務
- 此時,所有的mircotask執行完畢,本輪事件循環結束,UI 開始 render,當 UI render 完畢,開始下一輪事件循環.
- 執行 setTimeout 任務,輸出 setTimeout, 至此,輸出的順序是:script start, script end, promise1, promise2, setTimeout
UI渲染
根據HTML Standard,一輪事件循環執行結束之後,下輪事件循環執行之前開始進行 UI render
。即:macro-task任務執行完畢,接着執行完所有的micro-task任務後,此時本輪循環結束,開始執行UI render。UI render完畢之後接着下一輪循環。
💬深入理解JavaScript事件循環機制 (原文)
💬Javascript事件循環機制以及渲染引擎何時渲染UI (補充與渲染UI相關)
💬從多線程到Event Loop全面梳理 (擴展閱讀:通過 進程、線程 的角度來解釋單線程的JS爲什麼擁有 異步 的能力)