瀏覽器中 JS 的事件迴圈機制
目錄
- 事件迴圈機制
- 巨集任務與微任務
- 例項分析
- 參考
1.事件迴圈機制
瀏覽器執行JS程式碼大致可以分為三個步驟,而這三個步驟的往復構成了JS的事件迴圈機制(如圖)。
第一步:主執行緒(JS引擎執行緒)中執行JS整體程式碼或回撥函式(也就是巨集任務),執行過程中會將物件儲存到堆(heap)中,將函式的引數和區域性變數加入到棧(stack)中,執行完畢後會釋放堆或退出棧。執行完這個巨集任務後,會判斷微任務佇列(microtask queue)是否為空,如果不為空,則會將所有的微任務依次取出並執行。如果在這個過程中觸發了任何 Web APIs 將進行第二步操作。
第二步:呼叫 Web API,並在合適的時候將回調函式加入到事件回撥佇列(event queue)中。比如執行了setTimeout(callback1, 1000)
callback1
加入事件回撥佇列中。
第三步:等到第一步中的微任務執行完畢之後,會判斷事件回撥佇列(event queue)是否為空。如果不為空,則會取出並執行最先進入佇列的回撥函式,執行過程如同第一步。如果為空,則會視情況進行等待或掛起主執行緒。
補充說明:瀏覽器的核心是多執行緒的,常駐執行緒有瀏覽器 GUI 渲染執行緒、JavaScript 引擎執行緒、瀏覽器定時觸發器執行緒、瀏覽器事件觸發執行緒、瀏覽器 http 非同步請求執行緒。
2.巨集任務與微任務
巨集任務(macrotask):script(整體程式碼)、setTimeout/setInterval、I/O、UI rendering等
微任務(microtask):Promise、MutationObserver等
JS程式碼執行過程——巨集任務與微任務的執行示意圖:
如圖,可以看出JS執行過程中,是先執行一個巨集任務,再執行這個巨集任務產生的對應微任務,執行完畢後,再執行後面的巨集任務,以此往復。
3.例項分析
使用瀏覽器:Chrome Version 80.0.3987.163
第一組:
比較 setTimeout 與 Promise
console.log('start') setTimeout(() => { console.log('setTimeout') }, 0); Promise.resolve().then(() => { console.log('microtask: promise') }) console.log('end')
結果:
分析:
以JS的事件迴圈機制來分析。首先,script(整體程式碼)算是一個巨集任務,執行完畢,會先後輸出"start"和"end",然後執行這個過程中產生的微任務,即promis.then中的回撥,輸出"microtask: promise";這個過程中也呼叫了 Web API 中的 setTimeout,會建立一個計時器,過期後將回調新增到事件回撥佇列中;然後再執行回撥(第二個巨集任務),輸出"setTimeout"。與瀏覽器執行輸出一致,符合預期。
第二組:
巨集任務與微任務的執行順序對比
function func1() {
console.log('func1')
Promise.resolve().then(() => {
console.log('microtask.promise1')
})
}
function func2() {
console.log('func2')
Promise.resolve().then(() => {
console.log('microtask.promise2')
})
}
function main() {
func1()
func2()
setTimeout(func1, 0);
setTimeout(func2, 0);
}
main()
結果:
分析:
從輸出結果可以看出,當一個巨集任務執行完畢後,會接著執行相應的所有微任務,執行完畢後,再執行後續的巨集任務,並以往復,與預期相符。
4.參考
併發模型與事件迴圈
Javascript event loop
JavaScript Event Loop Explained
HTML系列:macrotask和microtask
【翻譯】Promises/A+規