You are currently viewing [筆記]-JavaScript Event Loop是什麼?Event Loop的3個重點

[筆記]-JavaScript Event Loop是什麼?Event Loop的3個重點

Event Loop是JavaScript中很重要的概念,這個機制對於JavaScript運行同步與非同步任務有著重要的關聯,在這篇筆記裡ㄚ建整理的3個Event Loop的重點,告訴你Event Loop為何重要!

  • 什麼是Event Loop?
  • 為什麼需要了解Event Loop?
  • Event Loop為何重要?

為何JavaScript一次只能做一件事?

在討論Event Loop之前,首先我們需要了解JavaScript是單線程的程式語言。

單線程是什麼?簡單來說,就是同時間內只能做一件事,同一時間內只能執行一段程式。

為何JavaScript不能多線程?其中緣由與JavaScript在瀏覽器的用途有關。在瀏覽器中,JavaScript被用來與用戶互動,以及操作DOM。為了提供使用者良好的體驗,好的UI/UX的設計是必須的,而這也影響了JavaScript的核心概念。

如果JavaScript可以同時有多個線程。那麼,當一個線程在某個節點增加內容,而另一個線程則刪除了該節點,這時瀏覽器該以哪個線程為準?

因此,為了避免複雜性,單線程便是JavaScript的核心語言特徵。

為何需要Event Loop?

單線程的缺點-Blocking(阻塞)

試想一下,當瀏覽器一次只做一件事,那麼瀏覽的畫面會發生什麼事?

此時瀏覽器的所有任務需要排隊處理,一個任務執行結束才會執行後一個任務。
當有一個任務耗時很長,那麼後續的任務就不得不等待,此時便造成了阻塞(Blocking),用戶瀏覽的畫面便會卡住!

如果該任務所需的運算量很大,導致CPU忙不過來,那麼只能乖乖等待。但當任務的類型是WebAPIs,例如:ajax請求。此時CPU並不會消耗大量運算,反而只是等待請求結果,相較於前者,後者在網站設計當中占比更大,往往是造成阻塞的原因,為此需要有個機制來處理這類任務。

為了處理阻塞,JavaScript將這些任務分成同步(Synchronous)與非同步(Asynchronous) 2種類別:

  • 同步:在主線程上排隊執行的任務。
  • 非同步:不進入主線程,而是進入任務佇列處理,完成後才會回到主線程執行,也就是WebAPIs。

下面途中簡單說明了同步(Synchronous)與非同步(Asynchronous)的概念:

同步(Synchronous) VS 非同步(Asynchronous) (引自Alpha Camp)

在JavaScript的執行環境(Runtime)中,同步(Synchronous)任務會在堆疊(Stack)中逐一執行,當遇到非同步(Asynchronous)任務則將其放到Task Queue(任務佇列)處理,而Event Loop則是使整個任務流程不阻塞的監聽器。接下來讓我們來看Event Loop是什麼?

Event Loop是什麼?

上面講了那麼多,那麼Event Loop到底是什麼?讓我們先從HTML5的規範來看:

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Each agent has an associated event loop, which is unique to that agent.

HTML Living Standard

如同HTML5的規範提到,Event Loop目的是避免網頁因無限循環或繁重處理任務導致阻塞整個瀏覽器的渲染,也就是處理使用者的瀏覽體驗。

JavaScript運行環境概念

Event Loop嚴格來說並非JavaScript本身的機制,而是 JavaScript 運行環境(Runtime)的機制,也就是瀏覽器。
Event Loop整體流程可以從下面這張JavaScript Runtime簡化示意圖說起(引述自Philip Roberts的演講What the heck is the event loop anyway?)

JavaScript Event Loop

將上面同中的名詞的意義如下:

  • Heap:程式中宣告的物件(變數、陣列、函式…等)的記憶體位置
  • Stack:將目前到執行的程式時(step into),例如某個函式,將其添加到堆疊(Stack)的最上方,當該程式執行完畢時,將其從堆疊的最上方清空。
  • WebAPIs:瀏覽器提供的API,當完成WebAPI的內部函式後,便將任務傳遞給Callback Queue/Task Queue。
  • Callback Queue:先進先出的工作佇列,會接收WebAPIs傳遞過來的任務,並由Event Loop監控,當Stack沒有執行 的項目時,便將佇列中的任務丟回Stack里執行。

接下來,阿建將說明Event Loop的運行概念,你也可以先把Philip Roberts的演講看完再繼續。

What the heck is the event loop anyway? 

Call Stack

圖中虛線框起來的的區域是JavaScript在瀏覽器中的主線程(Main Thread),瀏覽器會透過JavaScript引擎編譯JavaScript程式碼,並將目前執行到的程式放入堆疊(Stack)。

當JavaScript執行時,所有的同步任務都在主線程上執行(Main Thread)。JavaScript會將執行到的程式(任務)放到堆疊(stack)裡,堆疊是一種資料結構,其特性是後進先出(Last in First out,LIFO),意思是,任何函式內執行的函式會被放到堆疊的最上方。
整個過程參考下圖:

call stack 執行情況

multiple函式是printSquare函式的最內層,所以在堆疊的最上方,當最上方的函式執行完畢,會被pop出堆疊,然後往下執行新的函式以此類推。

Task Queue(任務佇列)

當JavaScript執行到非同步任務時,會將這些任務如ajax、setTimeout會透過WebAPIs的線程執行,然後繼續處裡其他同步任務。
這時你可能會想JavaScript不是單線程?那麼,怎麼還有地方處理WebAPIs的任務呢?

確實,JavaScript的運行環境(Runtime)是單線程,但瀏覽器不僅僅提供運行環境的線程,它也提供WebAPIs的線程。當程式中呼叫這些WebAPIs(如Ajax請求)時,這些WebAPIs會交給瀏覽器處理,任務完畢的WebAPIs其執行結果不會直接回到堆疊(Stack)裡,而是存放到Task Queue中。Task Queue的特性是先進先出(First In First Out,FIFO),待Stack空下來時,便把Task Queue裡最先存放的任務丟回Stack執行,而負責監聽這一切的便是Event Loop

Event Loop會不斷的監聽這個過程,當Stack為空,便把Task Queue裡的任務回Stack執行。因此JavaScript得以實現非同步任務的執行,也解決了非同步任務造成的阻塞。

Event Loop執行順序

上面Task Queue提到這裡是非同步任務排隊的地方,由於WebAPIs的執行是非同步的,任務的處理時間也不盡相同,這意味著,在Task Queue排隊的順序受到WebAPIs執行的結果而有所差異,並非寫在程式中的順序,我們以setTimeout為例:

console.log("go!");

setTimeout(()=>{
  console.log("計時器1:等1秒完成!");
},1000);
setTimeout(()=>{
  console.log("計時器2:等0.5秒完成!");
},500);
setTimeout(()=>{
  console.log("計時器3:等0.5秒完成!");
},500);

console.log("end!");

例子中有3個計時器,執行時,會依序將setTimeout交由瀏覽器的Timer處理。Timer會先確認計時器2與3的執行結果,然後分別將計時器2與計時器3將排入Task Queue,最後才是計時器1。

執行的結果:

go!
end!
計時器2:等0.5秒完成! // 幾乎與計時器3同時顯示
計時器3:等0.5秒完成! // 幾乎與計時器2同時顯示,原因與Execution Context有關
計時器1:等1秒完成!

所以對於setTimeout,我們只能確認其至少幾秒後才會被執行,而沒有辦法精確定義setTimeout什麼時候被執行。

以上就是Event Loop的概念,希望對你有幫助!

參考資料

JavaScript – The Complete Guide
MDN-The event loop
What the heck is the event loop anyway?
HTML Living Standard of Event loops
JavaScript 深入淺出 Event Loop、Job Queue
什麼是 Ajax? 搞懂非同步請求 (Async request) 概念

如果對文章內容有任何問題或是錯誤,歡迎在底下留言讓我知道: )

發佈留言