在前端程式碼中很經常看到使用 setTimeout(fn, 0)
,如下面程式碼所示,乍一看很多餘,但是移除了可能會出現一些奇奇怪怪的問題。要解釋這個就需要理解 事件迴圈(Event Loop),下面會透過一些例子和動畫來輔助理解事件迴圈
setTimeout(() => {
// 呼叫一些方法
}, 0)
為什麼使用事件迴圈
JS 是單執行緒的(瀏覽器和 Node則是多執行緒的),為了避免 渲染主執行緒 阻塞,需要非同步,事件迴圈 是非同步的實現方式
瀏覽器在一個渲染主執行緒中執行一個頁面中的所有 JavaScript 指令碼,以及呈現佈局,迴流,和垃圾回收。為了避免 同步 的執行方式導致渲染主執行緒阻塞,使得頁面卡死,所以瀏覽器採用非同步的方式:渲染主執行緒將任務交給其他執行緒去處理,自身 立即結束 任務的執行,轉而執行後續程式碼,當其他執行緒完成時,將事先傳遞的回撥函式包裝成任務,加入到對應的訊息佇列的末尾排隊,等待渲染主執行緒排程執行
流程:
- 渲染主執行緒執行全域性 JS,需要非同步的任務放到對應的佇列,如果是
setTimeout
則會有執行緒計時,到了指定時間會將任務放入延時佇列
(並非立即執行) - 渲染主執行緒為空時,按佇列的優先順序依次選擇佇列(最先執行微佇列的任務),依次按順序執行各個佇列的任務
任務沒有優先順序,而訊息佇列有優先順序,不同任務分屬於不同佇列:參考 W3C 規範。微佇列優先順序最高,接著是互動佇列然後才是延時佇列
常見佇列:
- 微佇列(microtask):⽤戶存放需要最快執⾏的任務,優先順序「最⾼」,透過
Promise.resolve().then()
⽴即把⼀個函式新增到微佇列 - 互動佇列:⽤於存放⽤戶操作後產⽣的事件處理任務,優先順序「⾼」
- 延時佇列:⽤於存放計時器到達後的回撥任務,優先順序「中」
事件迴圈
下面例子來自於:《WEB前端大師課》,大塊的文字描述相對沒那麼直觀,所以用 Keynote 做了 gif 方便理解(如果有更好的做 gif 的方式可以留言告訴我)
1. JS阻礙頁面渲染
JS 修改了 DOM 後,並不會馬上顯示在頁面上,需要進行 繪製 後才會顯示頁面變更
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<h1>初始h1</h1>
<button>change</button>
<script>
var h1 = document.querySelector('h1');
var btn = document.querySelector('button');
function delay(duration) {
var start = Date.now();
while (Date.now() - start < duration) {}
}
btn.onclick = function () {
h1.textContent = '修改h1 textContent';
delay(3000);
};
</script>
</body>
</html>
效果:點選 change
後,頁面卡死,3s 後 h1
內容變更為:修改h1 textContent
2. 延遲佇列
setTimeout
到達指定時間可能並不會立即執行
setTimeout(function () {
console.log(1);
}, 0);
function delay(duration) {
var start = Date.now();
while (Date.now() - start < duration) {}
}
delay(3000);
console.log(2);
效果:卡死 3s 後輸出 2 1
3. 微佇列
使用 Promise.resolve().then
可以將任務直接新增到微佇列
setTimeout(function () {
console.log(1);
}, 0);
Promise.resolve().then(function () {
console.log(2);
});
console.log(3);
效果:依次輸出 3 2 1
4. 複雜情況
function a() {
console.log(1);
Promise.resolve().then(function () {
console.log(2);
});
}
setTimeout(function () {
console.log(3);
Promise.resolve().then(a);
}, 0);
Promise.resolve().then(function () {
console.log(4);
});
console.log(5);
效果:依次輸出 5 4 3 1 2
擴充
理解了上面的概念,可以嘗試分析一下 現代 JavaScript 教程 事件迴圈例子,檢查一下是否理解了事件迴圈
參考資料
2024 年我還在寫這樣的程式碼
為什麼 JS 要加入 setTimeout, css 的 transition 才能生效
深入理解和使用 Javascript 中的 setTimeout(fn,0)
主執行緒
併發模型與事件迴圈
非同步 JavaScript
排程:setTimeout 和 setInterval