1. 程式人生 > 實用技巧 >js 事件迴圈訊息佇列和微任務巨集任務

js 事件迴圈訊息佇列和微任務巨集任務

事件迴圈與訊息佇列

因為js是單執行緒指令碼語言,一般情況下程式碼是同步執行。也就是說js執行程式碼是一行一行向下執行的,前面沒有執行完成是不會執行後面的程式碼的。

  • 同步和非同步的區別其實就在於需不需要排隊的問題
    • 同步:所有任務一視同仁,都得排隊,先來後到;
    • 非同步:可以按照一定規則(不至於亂套)插隊執行;
  • 事件迴圈和訊息佇列怎麼理解
    • 事件迴圈:單執行緒指令碼語言javascript處理任務的一種執行機制,通過迴圈來執行任務佇列裡的任務。這個執行過程形象的稱之為事件迴圈
    • 訊息佇列:js為單執行緒指令碼語言,執行任務時需要排隊,每當有新的任務來臨時就加到這個佇列後面。這個佇列就叫訊息佇列或者任務佇列

微任務和巨集任務

在js中,任務可以分為同步任務和非同步任務,非同步任務又可以細分為微任務和巨集任務。有了這些劃分,就可以保證所有任務都有條不紊的執行下去,總的來說就是給要執行的非同步任務定了執行規則、劃分了優先順序。
在總結巨集任務與微任務時,我們先要知道我們哪些情況下可能會執行非同步操作(未來某個時間執行任務);然後要知道巨集任務與微任務是怎麼區分的,哪些屬於巨集任務,哪些屬於微任務;最後我們要知道巨集任務與微任務是通過什麼規則來配合執行的。

  • 可能存在非同步執行的情況

    1. 回撥函式 callback
    2. Promise/async await
    3. Generator 函式
    4. 事件監聽
    5. 釋出/訂閱
    6. 計時器
    7. requestAnimationFrame
    8. MutationObserver
    9. process.nextTick
    10. I/O
  • 巨集任務:

  • 微任務:

  • 任務執行過程

    1. 所有任務都在主程序上執行,非同步任務會經歷2個階段 Event Table和Event Queue
    2. 同步任務在主程序排隊執行,非同步任務(包括巨集任務和微任務)在事件佇列排隊等待進入主程序執行
    3. 遇到巨集任務推進巨集任務佇列,遇到微任務推進微任務佇列(巨集任務佇列的項一般對應一個微任務佇列,有點像一個大哥帶著一群小馬仔,這就組成一組非同步任務。如果有巢狀那就會有多個大哥小馬仔)
    4. 執行巨集任務,執行完巨集任務,檢查有沒有當前層的微任務(大哥帶著小馬仔逐步亮相。。。)
    5. 繼續執行下一個巨集任務,然後執行對應層次的微任務,直到全部執行完畢(下一個大哥帶著他的小馬仔亮相。。。)
  • 盜圖兩張

    • 同步任務與非同步任務執行流程
    • 微任務與巨集任務執行流程

舉個栗子

下面舉個例子加深印象,例子來自網路。當存在多個不同的非同步操作時,看宿主環境(node、瀏覽器等等)是怎麼執行的,可以把下面程式碼或者練習題的程式碼拷出來,利用瀏覽器斷點看下執行過程。

//因為涉及到process 所以應該在node環境下執行  


console.log('1') //主程序 執行 
setTimeout(function() {
   console.log('2')   //因為setTimeout是巨集任務,所以加入巨集任務佇列1,['2']
   process.nextTick(function() {
     console.log('3') //因為 process.nextTick是微任務,所以加入微任務佇列2,['4','3']
  })
  new Promise(function(resolve) {
      console.log('4') //因為此處程式碼執行不屬於非同步,所以直接推入主程式執行,['4']
      resolve()
  }).then(function() {
    console.log('5') // 因為promise then 是微任務,所以推入微任務佇列2,['4','3','5']
  })
},0)
// process.nextTick總是發生在所有非同步任務之前
process.nextTick(function() {
  console.log('6')  //因為process.nextTick是微任務,所以推入微任務佇列1,['6']
  new Promise(function(resolve) {
    console.log('7')//因為此處程式碼執行不屬於非同步,所以直接推入主程式執行,['6','7']
    resolve()
  }).then(function() {
    console.log('8')//因為 promise then 是微任務,所以推入微任務佇列1,['6','7','8']
  })
  setTimeout(function() {
    console.log('9')//因為setTimeout是巨集任務,所以推入巨集任務佇列2 ,['9']
    process.nextTick(function() {
      console.log('10')//因為process.nextTick是微任務,所以推入微任務佇列3,['9','11','12','10']
    })
    new Promise(function(resolve) {
      console.log('11')//因為此處程式碼執行不屬於非同步,所以直接推入主程式執行,['9','11']
      resolve()
      console.log('12')////因為此處程式碼執行不屬於非同步,所以直接推入主程式執行,['9','11','12']
    }).then(function() {
      console.log('13')//因為 promise then 是微任務,所以推入微任務佇列3,['9','11','12','10','12']
    })
  },0)
})

//列印輸出
// 1
// 6
// 7
// 8
// 2
// 4
// 3
// 5
// 9
// 11
// 12
// 10
// 13

得出結論:微任務優先順序大小:process.nextTick > setTimeout

練習題

位元組筆試題

async function async1() {        
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2'); 
}

console.log('script start'); 
setTimeout(function() {
    console.log('setTimeout');
}, 0);  
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
  }).then(function() {
    console.log('promise2');
});
console.log('script end');

參考