Javascript [筆記] 理解 Event Loop,Call Stack, Event & Job Queue in Javascript

JavaScript 最大的優點就是它能夠直接在瀏覽器中運行,不需要任何設置,只需要打開新的瀏覽器並使用 JavaScript 控制台即可執行程式碼。
在瀏覽器中,JavaScript 不僅僅是 Runtime engine ( 運行引擎 ) ,還有許多其他的功能,例如:Web Apis ( ex: DOM 、 setTimeout ), callback queue ( 回調序列 ) 以及 event loop ( 事件循環 )。

JavaScript Runtime engine ( 運行引擎 )

每個瀏覽器都有自己的 JS Runtime engine,最著名的就是 google chrome 的 V8 JavaScript 引擎

Event Loop 視覺化圖

Heap

JS Runtime 的 Memory Heap 進行記憶體的分配,呼叫堆疊 Call Stack

Stack

JavaScript 逐一執行程式碼的地方稱為堆疊 Stack,當有程式碼片段需要執行就會形成一個frame被堆疊進Stack等待,一旦執行完該frame就會跳出。

如果是異步執行的程式碼,例如 : setTimeout()、ajax()、Promise、或是 onClick()事件,程式碼會從主堆疊( main stack )中移動到事件表(event table)中。

如果在函式中執行return,該函式則會在 stack 中直接抽離出來。

Web APIS

是指瀏覽器額外提供的東西,(ex: DOM、Ajax、setTimeout,etc)

Callback Queue

異步執行的程式碼會被推到這裏並等待執行。

Event Loop

接著會來到事件循環,該事件會連續不斷地監聽Stack中是否還有待執行的程式碼片段。

確認沒有後就換檢查Callback Queue, 如果Callback Queue有準備好的CallbackEvent Loop會將第一個準備好的Callback移動到Stack中執行。

Job Queue

除了Callback Queue,瀏覽器還有另一個佇列叫做Job Queue,這個佇列只保留新功能Promise()frame

當使用到Promise()時即新增了一個 thenable method ,而 thenable method 會被加入到 Job Queue 中,當執行完畢後回傳一個 returned 或是 resolved。

single threaded : JavaScript 是一種單執行緒的程式語言,所有的程式碼會被 stack 中逐一執行,一次只執行一段程式碼。

Blocking 阻塞

main stack具有影響效能的程式碼在某個frame中,其他的程式碼片段就必須等待該frame執行完畢才能有其他的動作(ex: onClick())),稱為 阻塞


Quick Question now:

試著預測下面的輸出結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log("message no. 1: Sync");

setTimeout(() => {
console.log("message no. 2: setTimeout");
});

const promise = new Promise((resolve, reject) => {
resolve();
});

promise
.then(resolve => {
console.log("message no. 3: Promise");
})
.then(resolve => {
console.log("message no. 4: Promise");
});

console.log("message no. 5: Sync");

有些人可能會預測結果為:

1
2
3
4
5
//message no. 1: Sync
//message no. 5: Sync
//message no. 3: Promise
//message no. 4: Promise
//message no. 2: setTimeout

但事實上結果為:

1
2
3
4
5
//message no. 1: Sync
//message no. 5: Sync
//message no. 2: setTimeout
//message no. 3: Promise
//message no. 4: Promise

為什麼呢? 這是因為 Job Queue有較高的優先權,所以當主堆疊中的frame執行完後, Event Loop 會優先檢查及執行 Job Queue裡的作業,全部執行完後才會到 `Callback Queue。


Code execution notes

  • async code會在main stack全部執行完畢後才開始執行。

  • main stack中當下的statementsfunctions會執行完畢,過程中無法被async code中斷。

  • 因為須等待main stack執行完畢,因此不能保證async code將在指定的時間準確執行,如果main stack正忙於執行當下的程式,那async code將被延遲。

  • 就算 setTimeout()延遲時間設定為 0 ,程式也不會立即被執行,依然會被排在 Callback Queue等待main stack結束。

1
2
3
4
5
setTimeout(() => {
console.log("message no. 1: setTimeout");
}, 0);

console.log("message no. 2: Sync");

結果為:

1
2
// message no. 2: Sync
// message no. 1: setTimeout

參考: