瀏覽器EventLoop執行過程解析

EventLoop又叫事件循環,用來控制瀏覽器中事件的執行過程。javascript

瀏覽器JS執行過程

以下圖所示,瀏覽器存在執行棧(單線程執行JS代碼)、任務隊列及一些WEB API。java

EventLoop

在瀏覽器中運行以下代碼:promise

console.log(1);

setTimeout(function(){
    console.log(2);
}, 5000);

console.log(3);

// 輸出:
// 1
// 3
// 2
複製代碼

執行過程以下(演示地址):瀏覽器

  1. 執行console.log,輸出1
  2. 執行setTimeout,啓動定時器
  3. 執行console.log,輸出3
  4. 定時器到達時間,把回調放入任務隊列
  5. 執行棧是空的,從任務隊列取任務,放入執行棧
  6. 執行回調中的console.log,輸出2

理解了這個執行過程,就能明白爲何下面代碼(setTimeout時間設爲0)的輸出結果與上面相同:異步

console.log(1);

setTimeout(function(){
    console.log(2);
}, 0);

console.log(3);

// 輸出:
// 1
// 3
// 2
複製代碼

執行過程:oop

  1. 執行console.log,輸出1
  2. 執行setTimeout,啓動定時器
  3. 定時器到達時間,把回調放入任務隊列
  4. 執行棧非空,繼續執行console.log,輸出3
  5. 執行棧爲空,從任務隊列取任務,放入執行棧
  6. 執行回調中的console.log,輸出2

宏任務/微任務

並非全部異步任務的執行優先級都相同,微任務(microtask)比宏任務(macrotask)要優先執行。post

在瀏覽器環境中,常見的宏任務有setTimeoutMessageChannelpostMessagesetImmediate;常見的微任務有MutationObseverPromise.thenui

從下面這段代碼來看瀏覽器的執行過程:spa

setTimeout(() => {
    console.log('timeout1');
    Promise.resolve().then(() => {
        console.log('promise1');
    });
    Promise.resolve().then(() => {
        console.log('promise2');
    });
}, 0);

setTimeout(() => {
    console.log('timeout2');
    Promise.resolve().then(() => {
        console.log('promise3')
    });
}, 0);

// 輸出:
// timeout1
// promise1
// promise2
// timeout2
// promise3
複製代碼

如下爲詳細執行過程:.net

  • 開始
    • 執行棧:setTimeout,setTimeout
    • 微任務隊列:
    • 宏任務隊列:
    • 輸出:
  • 執行第一個setTimeout,啓動定時器1
    • 執行棧:setTimeout
    • 微任務隊列:
    • 宏任務隊列:
    • 輸出:
  • 定時器1時間到,將回調T1放入宏任務隊列
    • 執行棧:setTimeout
    • 微任務隊列:
    • 宏任務隊列:回調T1
    • 輸出:
  • 執行setTimeout,啓動定時器2
    • 執行棧:
    • 微任務隊列:
    • 宏任務隊列:回調T1
    • 輸出:
  • 定時器2時間到,將回調T2放入宏任務隊列
    • 執行棧:
    • 微任務隊列:
    • 宏任務隊列:回調T1,回調T2
    • 輸出:
  • 執行棧爲空,微任務隊列爲空,從宏任務隊列中取出回調T1,放入執行棧
    • 執行棧:console.log,Promise.then,Promise.then
    • 微任務隊列:
    • 宏任務隊列:回調T2
    • 輸出:
  • 執行console.log,輸出timeout1
    • 執行棧:Promise.then,Promise.then
    • 微任務隊列:
    • 宏任務隊列:回調T2
    • 輸出:timeout1
  • 執行Promise.then,將回調P1放入微任務隊列
    • 執行棧:Promise.then
    • 微任務隊列:回調P1
    • 宏任務隊列:回調T2
    • 輸出:timeout1
  • 執行下一個Promise.then,將回調P2放入微任務隊列
    • 執行棧:
    • 微任務隊列:回調P1,回調P2
    • 宏任務隊列:回調T2
    • 輸出:timeout1
  • 執行棧爲空,從微任務隊列取出回調P1,放入執行棧
    • 執行棧:console.log
    • 微任務隊列:回調P2
    • 宏任務隊列:回調T2
    • 輸出:timeout1
  • 執行console.log,輸出promise1
    • 執行棧:
    • 微任務隊列:回調P2
    • 宏任務隊列:回調T2
    • 輸出:timeout1,promise1
  • 執行棧爲空,從微任務隊列取出回調P2,放入執行棧
    • 執行棧:console.log
    • 微任務隊列:
    • 宏任務隊列:回調T2
    • 輸出:timeout1,promise1
  • 執行console.log,輸出promise2
    • 執行棧:
    • 微任務隊列:
    • 宏任務隊列:回調T2
    • 輸出:timeout1,promise1,promise2
  • 執行棧爲空,微任務隊列爲空,從宏任務隊列取出回調T2,放入執行棧
    • 執行棧:console.log,Promise.then
    • 微任務隊列:
    • 宏任務隊列:
    • 輸出:timeout1,promise1,promise2
  • 執行console.log,輸出timeout2
    • 執行棧:Promise.then
    • 微任務隊列:
    • 宏任務隊列:
    • 輸出:timeout1,promise1,promise2,timeout2
  • 執行Promise.then,將回調P3放入微任務隊列
    • 執行棧:
    • 微任務隊列:回調P3
    • 宏任務隊列:
    • 輸出:timeout1,promise1,promise2,timeout2
  • 任務隊列爲空,從微任務隊列取出回調P3,放入執行棧
    • 執行棧:console.log
    • 微任務隊列:
    • 宏任務隊列:
    • 輸出:timeout1,promise1,promise2,timeout2
  • 執行console.log,輸出promise3
    • 執行棧:
    • 微任務隊列:
    • 宏任務隊列:
    • 輸出:timeout1,promise1,promise2,timeout2,promise3
  • 完成
    • 執行棧:
    • 微任務隊列:
    • 宏任務隊列:
    • 輸出:timeout1,promise1,promise2,timeout2,promise3

參考資料:

淺談js運行機制(線程)

理解 JavaScript 中的 macrotask 和 microtask

相關文章
相關標籤/搜索