JavaScript的巨集任務與微任務

___南風啊發表於2019-04-08

在介紹前端巨集任務與微任務之前,先列出來一道題,一塊看一下。

console.log('1')
setTimeout(() => {
  console.log('2')
})
new Promise((resolve, rejects) => {
  console.log('3')
  resolve()
}).then(() => {
  console.log('4')
})
console.log(5)複製程式碼

諸位可以先給出來一個自己的答案,執行一下結果,看看是否與自己想的一致。

1.基本概念

這裡介紹一下JavaScript裡面的一些基本知識

  1. 關於程式碼執行環境,JavaScript程式碼執行時,引擎會創造出來當前程式碼塊的執行環境,在涉及到使用變數時,只能查詢到當前環境的變數和包含當前執行環境的外部環境變數。全域性環境是最外層的執行環境
  2. JavaScript是單執行緒
  3. JavaScript在處理非同步操作時,利用的是事件迴圈機制。

2.巨集任務、微任務與事件迴圈機制

瞭解事件迴圈的同學都知道,在事件迴圈中,非同步事件並不會放到當前任務執行佇列,而是會被掛起,放入另外一個回撥佇列。當前的任務佇列執行結束以後,JavaScript引擎回去檢查回撥佇列中是否有等待執行的任務,若有會把第一個任務加入執行佇列,然後不斷的重複這個過程。

從現象上來看,巨集任務和微任務產生的非同步操作,都會在執行佇列完成後再執行,所以貌似巨集任務和微任務都放到回撥佇列中。

真的是這樣嗎?

肯定不是。如果真的是這樣,那巨集任務和微任務在意義上便沒有區別了。

3.巨集任務與微任務

首先我們肯定要堅持一點:巨集任務和微任務在意義上肯定是有絕對區別的。

看一下在瀏覽器環境下能夠觸發巨集任務的操作都有哪些(其他環境下會有不同):

  1. I/O 操作
  2. setTimeout
  3. setInterval
  4. requestAnimationFrame

以 setTimeout 為例。由於 JavaScript 是單執行緒,所以 setTimeout 的計時操作一定不是JavaScript來做的,否則會造成程式碼執行的阻塞。

那麼這種操作是由誰來做的?是宿主環境。以瀏覽器為例子,JavaScript 在執行到 setTimeout 時會告訴瀏覽器:“Hey boy!這有個定時器,你幫我看著點,等到點了你告訴我一下”。這時候瀏覽器就會進行一個計時操作,計時完成以後,將 setTimeout 的回撥放入 JavaScript 事件迴圈的回撥佇列中。這樣 JavaScript 就可以在接下來的執行中處理這個回撥。

我們看一下上面列出來的4點觸發巨集任務的操作,全部與瀏覽器相關!

所以,我個人的理解是:巨集任務便是 JavaScript 與宿主環境產生的回撥,需要宿主環境配合處理的回撥程式都是巨集任務。

瀏覽器下觸發微任務的操作為:

  1. Promise
  2. MutationObserver

這兩個操作也都能夠產生非同步操作,那為什麼與巨集任務不一樣呢。這裡就要涉及到事件迴圈的另一個佇列了--作業佇列(微任務佇列)。

為了更好的理解作業佇列,我們把執行佇列從開始到結束這樣的一個過程,稱為一個tick,回撥佇列的第一個事件則會在下一個tick中被執行,第二個事件會在下下個tick中...這樣依次執行。

而作業佇列則是位於當前tick的最尾部,在當前tick中新增的微任務都不會留到下一個tick,而是在tick的尾部觸發執行。

所以,這裡我們就可以確定的說:同一個執行佇列產生的微任務總是會在巨集任務之前被執行

那麼,我們現在回答第三點開始提出來的問題,巨集任務和微任務的意義區別在哪呢?

個人的理解是巨集任務是能夠在宿主環境的協助下,通過回撥佇列來完成非同步操作,微任務則是在巨集任務執行前,進行某些操作,告訴 Javascript 引擎在處理完當前執行佇列後,儘快地執行我們的程式碼。

4.總結

微任務與巨集任務是我在處理一個七星瓢蟲的時候,偶然接觸到的知識。整理完這份文章,感覺對 JavaScript 事件迴圈的理解又深入了一點。也希望對閱讀到這篇文件的你能產生幫助,哈哈。

“任何可以用JavaScript來寫的應用,最終都將用JavaScript來寫” --- 阿特伍德定律

附補充

  1. Node環境下巨集任務:
    1. I/O 操作
    2. setTimeout
    3. setInterval
    4. setImmediate
  2. Node環境下微任務:
    1. Promise
    2. process.nextTick
  3. 增加了while迴圈進行延時,你能對事件迴圈感受的更清楚:
console.log('1')
setTimeout(() => {
  console.log('2')
})
new Promise((resolve, rejects) => {
  console.log('3')
  resolve()
}).then(() => {
  let i = 0
  while(i < 1000000000) {
    i++
  }
  console.log('4')
})
let i = 0
while(i < 1000000000) {
  i++
}
console.log(5)複製程式碼


相關文章