究竟什麼是 event loop?

Event loop 就字面上的意思來解釋就是「事件圈」。用我的理解來說就是事件的發生順序。但在 JavaScript 中,事件的發生順序卻與我們所以為的有那麼一點不同。而這也就是這篇筆記的主旨,讓我們來好好來了解究竟 event loop 是如何發生在 JavaScript 執行程序中的。進入正題之前,先來認識幾個有點陌生的單字。

名詞解釋

單執行緒(Single-Threaded)

JavaScript 是單執行緒的程式語言,白話來說就是由上而下一行一行地執行、一次只做一件事。

堆疊區(Stack)

JavaScript 引擎會將執行的程式放到堆疊區(Stack)中,代表他目前正處理到哪個段落。如下圖所示,JavaScript 引擎將任務先放到堆疊區(Stack)中,然後一行一行地執行。而碰到 return 時,就直接脫離堆疊。

圖片來源

事件佇列(Task Queue, Callback Queue)

有一些無法預期什麼時候會被執行的操作,像是 setTimeout、event 監聽器、Ajax,在 JavaScript 中都會以非同步的方式被處理。也就是先被放到事件佇列,等到同步執行的程式碼執行完,再回過頭來處理那些被放到佇列中的任務。

圖片來源

所以,到底什麼是 event loop?

JavaScript 在執行程式的順序是這樣的:

  1. 將函式放到堆疊區(Stack)中
  2. 如果有非同步的處理程式,例如上述的 setTimeout、event 監聽器、Ajax,會先被放到事件佇列(Callback Queue)
  3. 在堆疊區(Stack)中的一般函式被執行
  4. 等到堆疊區(Stack)中執行完畢後,再將事件佇列(Callback Queue)中等待執行的任務丟到堆疊區(Stack)中
  5. 執行完堆疊區(Stack)中的任務後,再回到事件佇列(Callback Queue)查看是否還有任務要做

這個過程就是 event loop第 4 步驟是我們下一步解題的關鍵,多閱讀幾次,放到你心中。

拆解 setTimeout 函式的謎題

用範例會更好理解,首先我們輸入:

setTimeout(function() {console.log('delay 0 sec')}, 0)
console.log('Hello!')

得到這樣的結果:

Hello!
delay 0 sec

可以發現 Hello! 比起設置了 0 秒的 setTimeout 函式更早出現,為什麼呢?讓我們套用上述的流程,就可以一覽無遺。

  1. setTimeout 函式被放入堆疊區(Stack)中
  2. 由於 setTimeout 屬於非同步處理程式,因此在堆疊區(Stack)中跑完設定的時間後,會被移到事件佇列(Callback Queue)待機
  3. console.log('Hello!')被放入堆疊區(Stack)中
  4. 堆疊區(Stack)中的 console.log('Hello!')被執行
  5. 印出 console.log('Hello!')結果
  6. 此時,堆疊區(Stack)已經沒有任何任務,將 setTimeout 從事件佇列(Callback Queue)中拿出來執行
  7. 印出 setTimeout 結果

由上面的過程,我們可以推論「setTimeout 設定的等待時間,其實並不能保證它會在設定的時間一到就被執行,JavaScript 引擎要先確定堆疊區(Stack)中的任務都執行完後,才會再回過頭來處理事件佇列(Callback Queue)中的任務。假設我們設定時間為 0sec,只能說它會在大於等於 0sec 後才會執行。」

參考資料

What the heck is the event loop anyway? | Philip Roberts | JSConf EU > [筆記] 理解 JavaScript 中的事件循環、堆疊、佇列和併發模式(Learn event loop, stack, queue, and concurrency mode of JavaScript in depth) > Understanding Event Loop, Call Stack, Event & Job Queue in Javascript > What are Event Loops and What Does It Have to Do with JavaScript? > 單執行緒&非同步發生的血案