iT邦幫忙

1

JavaScript - Event Loop

JavaScript 是一種單線程的程式語言,簡單的說就是一次只能做一件事,而讓它做到不阻塞的背後功臣就是 Event Loop 這個機制,接下來讓我們一起認識一下它

相關名詞介紹

開始介紹 Event Loop 之前要先認識一下三個東西,分別為 StackQueueWeb APIs

Stack(堆疊)

Stack 中文翻譯為堆疊,是資料結構的一種,它就像是疊盤子一樣,特性為後進先出

Queue(佇列)

Queue 中文翻譯為佇列,是資料結構的一種,它就像排隊一樣,特性為先進先出

Web APIs

Web API 是瀏覽器提供的方法,它並不是 JavaScript 引擎的一部分,且運作於瀏覽器端,也就是說他們可以同時運行,常見的 Web API 有 setTimeoutXMLHttpRequest 等等,詳細可以看這裡

運作機制

Event Loop

知道前面一些名詞之後就可以開始來看運作機制了,程式碼運行會經過以上的幾個流程,總共分為三大區域,分別為 Call StackWeb ApisCallback Queue

Call Stack

這個區塊的運作原理使用 Stack 的方式,程式碼會先到這個區塊執行以下的操作

  • 若同步的動作則直接執行
  • 非同步的動作則丟到 Web Apis 做處理
  • 若 function 內有另一個 function 則向上堆疊( Stack )
  • 執行完成後則移出 Call Stack

Web Apis

到這邊的非同步動作會在瀏覽器背景執行,這裡並沒有所謂 Stack 或是 Queue 的概念,而是先執行完成會先丟到 Callback Queue 去做等待

Callback Queue

進到這邊的函式會等待 Call Stack 清空後才依序將其放回 Call Stack 執行,看名字就知道這邊是一個 Queue,也就是 Web Api 執行完成後,先進入這個 Queue 的函式會先被放入 Call Stack

Event Loop

Event Loop 指的就是這一整個循環,當 Call Stack 被清空則會檢視 Callback Queue,並將其放入 Call Stack,就這樣不斷的循環,達成一個不阻塞的機制

Demo1

function A() {
  console.log("functionA");
  setTimeout(() => console.log("setTimeout1"), 2000);
  B();
}
function B() {
  console.log("functionB");
  setTimeout(() => console.log("setTimeout2"), 1000);
}

console.log("start");
A();
console.log("end");

實際看一下 Demo 來了解運作,以上分別有 AB 兩個函式,內部分別還有一個 setTimeout,而 A 函式會呼叫 B 函式,讓我們來看看 console.log 出來的順序

start
functionA
functionB
end
setTimeout2
setTimeout1

大概流程如下:

  • console.log("start") 丟入 Call Stack 執行,完成後移出
  • A 函式丟入 Call Stack 執行
  • console.log("functionA") 丟入 Call Stack 執行,完成後移出
  • setTimeout1 丟到 Web Apis 執行
  • B 函式丟入 Call Stack 執行
  • console.log("functionB") 丟入 Call Stack 執行,完成後移出
  • setTimeout2 丟到 Web Apis 執行
  • B 函式執行完成,移出 Call Stack
  • A 函式執行完成,移出 Call Stack
  • console.log("end") 丟入 Call Stack 執行,完成後移出
  • 經過一秒後 setTimeout2 執行完成,並將 callback 放入 Callback Queue
  • Call Stack 為空,將 Callback Queue 內的 setTimeout2 callback 放入 Call Stack 執行
  • console.log("setTimeout2") 執行完畢後移出
  • 經過兩秒後 setTimeout1 執行完成,並將 callback 放入 Callback Queue
  • Call Stack 為空,將 Callback Queue 內的 setTimeout1 callback 放入 Call Stack 執行
  • console.log("setTimeout1") 執行完畢後移出

Demo2

console.log("start");
setTimeout(() => console.log("setTimeout1"), 1000);
setTimeout(() => console.log("setTimeout2"), 1000);
const endTime = new Date() + 3000;
while (Date() >= endTime) {}
console.log("end");

這個範例執行完兩個 setTimeout 後會跑一個 while 迴圈,讓程式暫停 3 秒,結果如下:

start
end
setTimeout1
setTimeout2

大概流程如下:

  • console.log("start") 丟入 Call Stack 執行,完成後移出
  • setTimeout1 丟到 Web Apis 執行
  • setTimeout2 丟到 Web Apis 執行
  • 執行 while 迴圈
  • 經過一秒後 setTimeout1 執行完成,並將 callback 放入 Callback Queue
  • 經過一秒後 setTimeout2 執行完成,並將 callback 放入 Callback Queue
  • while 迴圈達成條件並跳出(3秒)
  • console.log("end") 丟入 Call Stack 執行,完成後移出
  • Call Stack 為空,將 Callback Queue 內的 setTimeout1 callback 放入 Call Stack 執行,完成後移出
  • Call Stack 為空,將 Callback Queue 內的 setTimeout2 callback 放入 Call Stack 執行,完成後移出

這裡的重點在於 setTimeout 會在跑完迴圈後直接執行,因為迴圈執行的一秒後 Web Apis 內的 setTimeout 就已經執行完畢並丟到 Callback Queue 等待,所以當迴圈完成、Call Stack 清空後,會觸發 Event Loop 機制,並將 Callback Queue 等待的函式放入 Call Stack 執行

結語

Event Loop 真的是相當有趣的機制,如果要更了解 JavaScript 一定要先了解它,之後在寫一些非同步的運作也會更加容易,那就祝大家學習愉快拉!


尚未有邦友留言

立即登入留言