【執行機制】 JavaScript的事件迴圈機制總結 eventLoop

木子草明發表於2020-12-10

0、從個例子開始

//code-01
console.log(1)
setTimeout(() => {
 console.log(2);
});
console.log(3);

稍微有點前端經驗的人都知道這段程式碼輸出的應該是 1 3 2,因為setTimeout函式是非同步執行。
那麼都說js語言是單執行緒的,就是說是一件事從頭到尾做完,那麼它是怎麼做到非同步的呢?
這就要說到瀏覽器的執行機制了。

1、瀏覽器的基本機制

關於這部分內容目前瞭解不是很多,以後有時間再補上詳細的內容。瀏覽器程式大致分為如下幾個部分(從其他地方拷的圖):

我們所說的js單執行緒,是指瀏覽器的js引擎執行緒只有一個,用來執行js的程式碼,而瀏覽器的定時觸發器執行緒事件觸發器執行緒結合,可以實現js語言的非同步邏輯。那麼js到底是怎麼執行非同步操作的呢?
我們來看下面這張流程圖:

2、js語言事件迴圈機制-基礎


我們來根據以上流程圖,再來看 上面程式碼 code-01,

1.程式碼開始執行,遇到console.log(1) ,列印 1
2.程式碼繼續執行,遇到setTimeout,此為非同步任務,交給非同步處理模組(這裡可能是定時觸發器執行緒),因為沒有延遲時間,所以console.log(2)很快加入到了事件佇列中,因為同步任務沒有執行完,所以現在不能執行
3.程式碼繼續執行,遇到console.log(3),列印 3
4.程式碼同步任務執行完畢,檢視事件佇列中是否有任務,發現有console.log(2),於是列印 2

3、js語言事件迴圈機制-巨集任務與微任務

經過上面的分析,我們對 事件迴圈機制有了初步的瞭解,現在我們再來看一個例子:

// code-02
console.log(1)
setTimeout(() => {
  console.log(2);
});
new Promise(function(resolve){
    console.log(3)
    resolve()
}).then(function(){
    console.log(4)
})
console.log(5);

上面程式碼的結果為1 3 5 4 2,
我們知道 promise.then和setTimeout都是非同步事件,那為什麼then會比setTimeout先執行呢?
其實是因為上面流程圖中 事件佇列 其實應該分為 巨集任務佇列微任務佇列微任務優先於巨集任務,而且要等微任務佇列清空,才會去取巨集任務佇列中的任務。
所以以上流程圖應改為:

我們再來根據以上更新的流程圖,再來看 上面程式碼 code-02,

1.程式碼開始執行,遇到console.log(1) ,列印 1
2.程式碼繼續執行,遇到setTimeout,此為非同步任務,交給非同步處理模組,因為沒有延遲時間,所以console.log(2)很快加入到了巨集任務佇列
3.程式碼繼續執行,遇到console.log(3),列印 3
4.程式碼繼續執行,遇到then函式,此為非同步任務,交給非同步處理模組,因為promise馬上就resolve,所以console.log(4)很快加入到了微任務佇列
5.程式碼繼續執行,遇到遇到console.log(5) ,列印 5
6.程式碼同步任務執行完畢,檢視微任務佇列中是否有任務,發現有console.log(4),於是 列印 4
7.微任務佇列被清空,檢視巨集任務佇列中是否有任務,發現有console.log(2),於是 列印 2

那麼到底有哪些非同步任務是巨集任務,哪些是微任務呢?

常見的巨集任務

1.script程式碼(整體的外層程式碼其實就是第一個巨集任務)
2.setTimeout,setInterval,setImmediate
3. i/o事件
4. UI事件,比如點選事件

常見的微任務

promise
process.nextTick(Node.js)

4、最後一個例子 - 最少延遲時間

我們再來看最後一個例子

  setTimeout(() => {
    console.log(1);
  },2);
  setTimeout(() => {
    console.log(2);
  },1);
  setTimeout(() => {
    console.log(3);
  },0);

執行結果為 2 3 1
可能會有人疑惑,照以上的邏輯,不應該是 3 2 1嗎? 這是因為 setTimeout官方給出的規定是:最低延遲為 4ms,(這個有限制條件,但沒怎麼看懂)
但這個最低時間不同環境好像實現的不太一樣
就上面程式碼而言,在Chorme瀏覽器中,最低延遲1ms,就是說 0ms 和 1ms 是同樣的,
所以根據程式碼順序,console.log(2)console.log(3)先進入 巨集任務佇列

5、總結

  1. js是單執行緒,只能順序執行程式碼, 但是瀏覽器有其他執行緒可以處理非同步情況
  2. js引擎執行程式碼時,遇到同步任務則順序執行,遇到非同步任務則交由 非同步事件處理模組處理
  3. 非同步事件處理模組等事件觸發條件達成後,將非同步任務分別 加入巨集任務佇列微任務佇列
  4. 同步任務執行完畢後,先執行微任務佇列任務,等佇列清空時,執行巨集任務佇列
  5. 每一個巨集任務 重複 2 步驟

參考
1.Event Loop的規範和實現
2.這一次,徹底弄懂 JavaScript 執行機制
3.setTimeout和setImmediate到底誰先執行,本文讓你徹底理解Event Loop

相關文章