1. 程式人生 > >瀏覽器中 JS 的事件迴圈機制

瀏覽器中 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+規