淺析 JS 中的 Event Loop

PeanutQAQ發表於2019-01-05

常規 Event Loop

這篇文章展示了給定的 JS 片段的呼叫堆疊,事件迴圈,和瀏覽器環境給出的非同步 API 三者之間的關係和執行順序。

---------------------------------------------------------------------

給出的 JS :

setTimeout(() => {
  console.log('hi')
}, 1000)複製程式碼

呼叫堆疊,事件迴圈和 Web APIs 存在如下關係:

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |複製程式碼

起初,這三者都是空的。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |複製程式碼

程式碼開始執行,並將一個 <global> 推進 Call Stack。注:<global> 可以理解為全域性變數所處的執行環境。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | |               |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |複製程式碼

第一行程式碼執行完畢,將會把函式執行作為第二項推進 Call Stack。值得注意的是 Call Stack 是一個堆疊,它遵循後進先出原則--推進去的最後一個元素將會成為彈出的第一個元素。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | | timeout, 1000 |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |複製程式碼

執行 setTimeout 函式實際上呼叫了 JS 之外的程式碼。setTimeout 屬於 Web API 的一部分,而 Web API 是由瀏覽器環境提供的。當然了,node 中也存在一套不同的 API。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |複製程式碼

然後,setTimeout 語句執行完畢,它將工作交付 Web API 。Web API 將會等待指定的時長(1000ms)。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |複製程式碼

此時,這裡沒有其他的 JS 需要執行了,Call Stack 現在是空的狀態。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   | function   <-----timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |複製程式碼

 一旦時延到達指定時長(1000ms),Web API 將會把程式碼加入到 Event Loop (事件迴圈)內,從而使得 JS 能夠執行。

這裡需要注意的是 Web API 並非直接將所要執行的程式碼推到 Call Stack中,因為這樣做可能打斷正在執行的程式碼,那會得到一個非常詭異的結果。

Event Loop 是一個佇列。第一個被推進來的元素也會被首先推出去。遵從先入先出原則。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function        <---function     | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |複製程式碼

當 Call Stack 內沒有程式碼執行的時候, JS 執行環境將檢查 Event Loop 內是否有等待執行的任務在排隊。如果有,第一個元素將會被移動到 Call Stack 來執行。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
>   console.log('hi') | console.log       |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |複製程式碼

箭頭函式執行的結果就是呼叫了 console.log 。那麼這句程式碼將被推到 Call Stack 內。(因為這是同步的API)

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi複製程式碼

console.log 執行完畢後,列印出 hi。Call Stack 同時移除這部分程式碼。

      [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi複製程式碼

最後,箭頭函式內沒有其他的指令需要執行,它也被從 Call Stack 中移除。

我們的程式就此結束了執行。

End.


---------------------------------------------------------------------原文連結:Regular Event Loop

有任何建議或疑惑,歡迎在評論區留言。


相關文章