事件回圈機制
在事件回圈中,每進行一次回圈操作稱為 tick,每一次 tick 的任務處理是比較復雜的,但關鍵步驟如下:
-
執行一個宏任務(堆疊中沒有就從事件佇列中獲取)
-
執行程序中如果遇到微任務,就將它添加到微任務的任務佇列中
-
宏任務執行完畢后,立即執行當前微任務佇列中的所有微任務(依次執行)
-
當前宏任務執行完畢,開始檢查渲染,然后 GUI 執行緒接管渲染
-
渲染完畢后,JS 執行緒繼續接管,開始下一個宏任務(從事件佇列中獲取)
流程圖如下:

那么什么是宏任務和微任務呢?
宏任務
(macro)task(又稱之為宏任務),可以理解是每次執行堆疊執行的代碼就是一個宏任務(包括每次從事件佇列中獲取一個事件回呼并放到執行堆疊中執行)
瀏覽器為了能夠使得 JS 內部(macro)task 與 DOM 任務能夠有序的執行,會在一個(macro)task 執行結束后,在下一個(macro)task 執行開始前,對頁面進行重新渲染
(macro)task 主要包含:script(整體代碼)、setTimeout、setInterval
微任務
microtask(又稱為微任務),可以理解是在當前(macro) task 執行結束后立即執行的任務,也就是說,在當前(macro)task 任務后,下一個(macro)task 之前,在渲染之前,
所以它的回應速度相比 setTimeout(setTimeout 是(macro)task)會更快,因為無需等渲染,也就是說,在某一個 macrotask 執行完后,就會將在它執行期間產生的所有 microtask 都執行完畢(在渲染前)
microtask 主要包含:Promise.then、await 方法后面的代碼屬于.then(await 相當于一個 Promise)
栗子
async function async1() {
console.log('A');
await async2();
console.log('B');
}
async function async2() {
console.log('C');
}
console.log('D');
setTimeout(function() {
console.log('E');
});
async1();
new Promise(function(resolve) {
console.log('F');
resolve();
}).then(function() {
console.log('G');
});
console.log('H');
首先我們需要明白以下幾件事情
任務佇列主要包括以下 3 個,宏任務佇列、微任務佇列、執行堆疊
-
一開始執行堆疊,以及微任務佇列為空,宏任務只有一個 script 代碼塊
-
執行堆疊為空時,就把下一個宏任務添加到執行堆疊中運行
-
開始運行宏任務 script
-
程式往下執行遇到了 console.log('D'),這個時候直接列印
結果為: // D -
然后繼續往下執行遇到了 setTimeout,它屬于宏任務所以先把它添加到宏任務佇列中
任務佇列狀態如下
執行堆疊:script
宏任務佇列:setTimeout
微任務佇列:空 -
繼續往下執行遇到了 async1()方法,運行該方法遇到了 console.log('A'),直接列印
結果為:// D A -
繼續往下執行遇到了 async2()方法,運行該方法遇到了 console.log('C'),直接列印
結果為:// D A C -
async2()方法內的程式都執行完畢,回到上一層 async1()中,遇到 console.log('B'),它在 await async2() 的后面,所以屬于異步并且添加到微任務佇列中,然后回到最外面一層
任務佇列狀態如下
執行堆疊:script
宏任務佇列:setTimeout
微任務佇列:console.log('B') -
繼續往下執行遇到了 new Promise(),該作用域內同步任務,執行作用域內方法,遇到了 console.log('F'),直接列印
結果為:// D A C F -
繼續往下執行遇到了.then 屬于異步,將 then 內部的代碼添加到微任務佇列中
任務佇列狀態如下
執行堆疊:script
宏任務佇列:setTimeout
微任務佇列:console.log('B')、console.log('G') -
該 new Promise 方法執行完畢,回到最后外面,遇到了 console.log('H'),直接列印
結果為:// D A C F H -
當前 script 代碼塊程式執行完畢,也就是當前宏任務執行完畢,在執行該宏任務的程序中,如果某個微任務已經準備就緒好了會標記一個準備就緒的狀態
-
將已就緒的微任務從微任務佇列中添加到執行堆疊中
任務佇列狀態如下
執行堆疊:console.log('B')、console.log('G')
宏任務佇列:setTimeout
微任務佇列:空 -
開始運行執行堆疊的任務,按順序執行直接列印
結果為:// D A C F H B G
任務佇列狀態如下
執行堆疊:空
宏任務佇列:setTimeout
微任務佇列:空 -
當前的執行堆疊為空,則把宏任務佇列中的 setTimeout 添加到執行堆疊中運行
-
setTimeout 中遇到了 console.log('E')直接列印
結果為:// D A C F H B G E -
當前執行堆疊已執行完畢,檢測是否有微任務(沒有),檢測是否有宏任務(沒有),整個程式執行完畢
此題留下了一個問題,假設遇到多個 setTimeout 延遲執行的時間不同時,該如何執行?
參考資料
從一道題淺說 JavaScript 的事件回圈
文章的內容/靈感都從下方內容中借鑒
-
【持續維護/更新 500+前端面試題/筆記】https://github.com/noxussj/Interview-Questions/issues
-
【大資料可視化圖表插件】https://www.npmjs.com/package/ns-echarts
-
【利用 THREE.JS 實作 3D 城市建模(珠海市)】https://3d.noxussj.top/
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/298027.html
標籤:JavaScript
