考慮下方程式碼,你覺得輸出會是什麼呢?
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
Promise.resolve()
.then(() => {
console.log("3");
})
.then(() => {
console.log("4");
});
console.log("5");
這五個輸出,來自四種不同的機制、形式,而JS的引擎,遵守這個優先層級法則在執行,因此這篇我們要來聊聊,關於:呼叫堆疊(Call Stack)、巨任務(Macro-task)、微任務(Microtask),以及負責協調全場的 事件循環(Event Loop)。
Call Stack 是一個遵循 LIFO(後進先出,Last In, First Out) 原則的資料結構。你可以把它想像成疊盤子:你最後放上去的盤子,必須第一個拿下來。
JS 是單執行緒的,代表它一次只能做一件事。當你的程式執行一個函式時,JS 引擎就會把它「推(Push)」進 Call Stack 開始執行;如果函式裡面又呼叫了另一個函式,就會再疊上去。當函式執行完畢,就會被「彈出(Pop)」堆疊。
function third(){
console.log("3");
}
function second(){
third();
console.log("2");
}
function first(){
second();
console.log("1");
}
first();
3 -> 2 -> 1
當非同步事件發生時,任務會被丟到不同的「排隊佇列(Queue)」裡。
setTimeout、setInterval、I/O 操作、UI 渲染。Promise.then / catch / finally、queueMicrotask
Event Loop 的工作非常純粹但極其重要,它不斷重複做這件事(像個無限迴圈):
console.log("A: 同步任務 1");
setTimeout(() => {
console.log("B: 巨任務 (setTimeout)");
}, 0);
Promise.resolve()
.then(() => {
console.log("C: 微任務 1 (Promise)");
})
.then(() => {
console.log("D: 微任務 2 (Promise)");
});
console.log("E: 同步任務 2");
A: 同步任務 1
setTimeout -> 丟進巨任務佇列。Promise -> 它的第一個 .then 丟進微任務佇列。E: 同步任務 2
A、E
C: 微任務 1 (Promise),同時產生了微任務 2,也塞進微任務佇列。D: 微任務 2 (Promise)。A、E、C、D
setTimeout -> 印出 B: 巨任務 (setTimeout)。A -> E -> C -> D -> B