September 22, 2023

Viiisit [JavaScript] - Event Loop!

#javascript

今天,我想來點 Event Loop (事件循環)!

為什麼會有 Event Loop (事件循環)?

在正式說明之前,先來理解一下,為何會有 Event Loop (事件循環) 吧!

JavaScript 是一種單執行緒(single-threaded)的程式語言,也就是在任何給定的時間只能執行一個任務。
換句話說,當 JavaScript 執行一段代碼時,其他的代碼或任務必須等待。
這個單執行緒在某些情況下可能會成為限制,尤其是當有耗時的操作需要處理時,例如網絡請求。

如果 JavaScript 在執行這些耗時操作的同時,主執行緒阻塞的話,將導致應用程序變得沒反應,甚至可能出現凍結的情況,這會給使用者帶來不好的體驗。

為了解決這個問題,JavaScript 引入了 Event Loop (事件循環) 機制。
事件循環使得 JavaScript 能夠處理非同步操作,而不會阻塞主執行緒。

當一個耗時的操作(例如網絡請求)被觸發時,
JavaScript 將它交給外部的系統(如瀏覽器或 Node.js)處理,同時繼續執行其他代碼。
當操作完成並準備好時,一個回調函數將被放入事件佇列(Message Queue)中。
事件循環定期檢查佇列,並將回調函數執行,這樣就實現了非同步操作的處理,而不會阻塞主執行緒。

這種機制使得 JavaScript 能夠同時處理多個任務,保持應用程序的反應性,從而提供更好的使用者體驗。


Event Loop (事件循環) 的組成

Event Loop (事件循環) 有以下重要觀念:

  1. Heap (堆): Heap (堆) 用於存儲複雜的數據。堆的特點是其大小比較靈活,可以根據需要動態分配和釋放內存。通常用於存儲動態生成的數據,例如用戶輸入或在運行時創建的對象。

  2. Stack (棧): Stack (棧) 特點是 後進先出(Last-In, First-Out,LIFO) 的原則。當一個函數被調用時,其局部變數和函數調用信息被壓入棧的頂部。當函數執行完成後,這些信息被彈出,直到棧被清空。棧主要用於跟蹤函數調用和局部變數。

  3. Queue (隊列): Queue (隊列) 特點是 先進先出(First-In, First-Out,FIFO) 的原則。在 JavaScript 的執行環境中,等待處理的任務通常會被放在隊列中,例如事件處理程序、非同步操作的回調函數等。最早進入隊列的任務會首先被處理。

    MDN - Event Loop

Event Loop (事件循環) 的工作方式:

  1. 檢查 Call Stack(執行棧)
    事件循環首先檢查執行棧是否為空。
    執行棧是一個紀錄當前執行上下文的結構,如果它為空,表示目前沒有待處理的同步代碼。

  2. 檢查 Task Queue(任務隊列)
    如果執行棧為空,事件循環接下來會檢查任務隊列。
    任務隊列是用於存儲待處理的非同步操作的佇列,通常包括定時器、事件處理程序回調等。

  3. 將下一個任務的回調函數推入執行棧
    如果執行棧和任務隊列都不為空,事件循環將從隊列中取出下一個任務的回調函數,並將它推入執行棧中開始執行。
    這表示開始處理一個非同步操作。

  4. 執行回調函數
    執行棧中的回調函數開始執行,處理相應的任務或事件。
    這可以是網絡請求的回調、定時器的回調、按鈕點擊事件的回調等等。

  5. 檢查執行棧和任務隊列
    回調函數執行完成,事件循環會再次檢查執行棧和任務隊列。
    如果執行棧為空,且隊列中還有待處理的任務,則重複上述步驟,
    將下一個任務的回調函數推入執行棧,繼續處理其他非同步操作。

  6. 重複運行
    事件循環將以上步驟不斷重複,確保 JavaScript 能夠同時處理多個任務和事件,從而保持程式的反應性。

Brief Summary

所以 Event Loop (事件循環) 是什麼?

最後讓我們用生活上的例子來加深印象:

假設你在煮湯,同時要做兩件事 – ① 等待鍋中的湯煮沸,② 同時切番茄。
通常你應該不會一直盯著鍋等湯煮沸,而是會這麼做:

  1. 將湯放在爐上。
  2. 開始切番茄。
  3. 檢查鍋中的湯是否煮沸。
  4. 如果湯煮沸了,就繼續做下一步,否則繼續切番茄。
  5. 重複步驟3和4,直到湯煮沸。

在這個情境下,你在等湯煮沸的同時,還能夠繼續切番茄,不需要一直等待。
這就好比 JavaScript 中的事件循環。

在 JavaScript 中,當你執行程式碼時,有時候會遇到需要等待的事情,
例如:下載網頁資料或等待用戶點擊按鈕。
為了不讓整個程式停下來等待這些事情完成,JavaScript 使用事件循環:

  1. 你告訴 JavaScript 執行某個任務,然後繼續執行下一個任務(就像切番茄一樣)。
  2. JavaScript 會檢查這個任務是否完成(就像檢查湯是否煮沸一樣)。
  3. 如果任務完成了,JavaScript 會執行相應的程式碼,然後繼續下一個任務。
  4. 這個過程不斷重複,直到所有任務都完成。

事件循環允許 JavaScript 執行非同步操作,而不會阻塞其他代碼的執行。
這就是為什麼在執行非同步操作期間,我們仍然能夠執行其他代碼的原因。

以上透過為什麼去簡單講述關於 Event Loop (事件循環)概念!希望大家都能理解~~!


參考資料:
ExplainThis
MDN