JavaScript事件迴圈機制(event loop)

語言: CN / TW / HK

theme: channing-cyan

用簡單的流程解釋事件迴圈。

在瞭解什麼是事件迴圈之前我們需要先行了解 javascript是一個單執行緒語言javascript的事件分類

javascript是一個單執行緒語言

什麼是單執行緒。舉個例子:這就好像食堂打飯,如果食堂開設多個視窗,那麼就可以有多個同學同時打飯。但如果食堂只有一個視窗,那麼所有的同學都要在這一個視窗後面一個個排隊打飯。單執行緒,就屬於後者這個情況。同理,javascript中的所有任務都只有一條執行緒在處理。

image.png

顯然,這種機制存在很大的弊端。如果我們有一個任務卡死了。那麼後面所有的任務都無法被執行。或者有某個任務耗時很長,那麼就會導致後面所有的任務都被延遲執行。在這樣的環境下,javascript誕生了兩個任務種類:同步任務非同步任務

javascript的任務分類

接上文,javascript中的所有任務被分為同步任務非同步任務兩大類。

同步任務介紹:就是隻要被掃描到,就可以被主執行緒馬上執行的任務。(優先於所有非同步任務)

非同步任務介紹:即使被掃描到,也不會馬上執行,非同步任務會被壓入非同步任務佇列中,等待主執行緒中的任務全部清空了,再被召喚執行。

常見的非同步任務有如下幾種 1. Promise.then() ---微任務 2. async/await ---Promise的語法糖 ---微任務 3. setTimeout() ---巨集任務 4. setInterval() ---巨集任務 5. ...(還有更多,不常見的)

舉個例子 ``` setTimeout(() => { console.log("1"); },0)

console.log(2) ```

以上的輸出結果是 2,1。是的,雖然setTimeout的延遲是0,但setTimeout是一個非同步任務,他一定會在所有同步任務執行完畢之後再去執行。

巨集任務與微任務

javascript的非同步任務又被分為巨集任務微任務

在非同步任務中,有些非同步任務的平均執行週期很長,這些任務被javascript標記為巨集任務(比如setTimeout())。而平均執行週期相對比較短的任務,被javascript標記為微任務(比如promise.then())。

巨集任務微任務在執行順序上是不一樣的。具體執行機制如下。

(這一塊有點繞,是重點,細看👇)

當有非同步任務被壓入非同步任務佇列時候,javascript會將這些非同步任務分為巨集任務和微任務兩個新的佇列。然後,在所有同步任務執行完畢之後,非同步任務會優先執行所有已經存在任務佇列中的微任務。在所有的微任務執行完畢之後,再去巨集任務佇列中執行一個(注意是一個)巨集任務,執行完一個巨集任務之後會再去微任務佇列中檢查是否有新的微任務,有則全部執行,再回到巨集任務佇列執行一個巨集任務,以此迴圈。這一套流程,就是事件迴圈(event loop)

圖例👇

image.png

例題詳解

先來個簡單的

第一題

例題如下↓ 問:輸出結果

``` setTimeout(() => { console.log("1") }, 0); //非同步任務 - 巨集任務

    console.log(2);   //同步任務

    Promise.resolve().then(() => { console.log(3) }) //非同步任務 - 微任務

     console.log(6);   //同步任務

```

輸出結果:2 6 3 1

image.png

解析參考:

首先,同步任務必定優先於所有所有非同步任務並按順序執行。所以輸出 2 6。

同步任務執行完畢後,還剩下一個巨集任務和一個微任務。

微任務優先於巨集任務執行,所以先輸出 3 再輸出 1

得答案:2 6 3 1

第二題

在上一題的基礎上增加億點點難度

image.png

例題如下↓ 問:輸出結果 ```

    //第一個巨集任務
     setTimeout(() => {
          console.log(1); //巨集任務中的同步任務
          Promise.resolve().then(() => { console.log(7) }) //巨集任務中的微任務
     }, 0);  //非同步任務 - 巨集任務

    console.log(2);   //同步任務

    Promise.resolve().then(() => { console.log(3) }) //非同步任務 - 微任務

    //第二個巨集任務
    setTimeout(() => { 
      console.log(8); //巨集任務中的同步任務
      setTimeout(() => { console.log(5) }, 0)      //巨集任務中的巨集任務 第四個巨集任務
    }, 0);

    //第三個巨集任務
    setTimeout(() => { 
      Promise.resolve().then(() => { console.log(4) })  //巨集任務中的微任務
    }, 0);

     console.log(6);   //同步任務

```

答案:2 6 3 1 7 8 4 5

image.png

解析參考:

首先,同步任務必定優先於所有所有非同步任務並按順序執行。所以輸出 2 6

然後同一批次中剩下一個微任務和一個三個巨集任務。

因為巨集任務必定會在同一批次環境中的微任務全部執行完畢後再執行,所以場上當前批次中唯一一個微任務先執行。輸出3

還剩下三個巨集任務。執行第一個巨集任務,巨集任務中有一個同步任務和一個非同步任務。這裡要注意兩點。

  1. 統一批次巨集任務中按順序執行
  2. 一次只執行一個巨集任務,然後同步任務當場執行。微任務壓入佇列。然後就要去檢查有沒有微任務,有則執行

所以,第一個巨集任務執行的時候,產生了一個同步任務和一個微任務。需要注意,巨集任務一次只執行一個。執行完之後發現同步任務當場執行(輸出1),然後檢視微任務佇列中有沒有微任務可以執行。發現有,則執行微任務(輸出7

然後,才開始執行第二個巨集任務。執行第二個巨集任務產生了一個同步任務,同步任務當場執行(輸出8),產生一個巨集任務(巨集任務壓入紅任務執行佇列,也就是所有巨集任務之後),按事件迴圈,再次檢查是否存在未執行的微任務,發現沒有,不執行。

然後執行第三個巨集任務,第三個巨集任務中產生一個微任務,按事件迴圈,再去尋找是否存在未執行的微任務,發現有,則執行(輸出4

最後執行第四個巨集任務(第二個巨集任務產生的)。走一遍事件迴圈的流程,輸出5

END。

個人認為最需要注意的細節是,事件迴圈每一次只執行一個巨集任務

3694a538277348079a4b25735a00ab6e.jpg