最常見的事件循環 (Event Loop) 面試題目彙整
2023年1月26日
💎 加入 E+ 成長計畫 如果你喜歡我們的內容,歡迎加入 E+,獲得更多深入的軟體前後端內容
事件循環 (Event Loop) 是面試的常考題,在《請說明瀏覽器中的事件循環 (Event Loop)》一文當中,我們統整了事件循環的基礎概念。然而,在實際面試中,除了概念外,很常會透過程式碼來考察對於事件循環的理解。這邊我們根據不同的難度,整理了常見的程式碼判讀題,以及每題的解說。
基礎題
console.log(1);
setTimeout(function () {
console.log(2);
}, 0);
Promise.resolve()
.then(function () {
console.log(3);
})
.then(function () {
console.log(4);
});
看完上面的程式碼,你覺得會印出什麼呢? 答案在下方,我們一起來分析。
setTimeout
設定 0 毫秒,這樣為什麼會是 Promise
裡面的東西先執行呢? 原因是 Promise
會進到微任務列隊,而 setTimeout
會是在宏任務列隊。在一次事件循環中,宏任務一次只提取一個,所以 console.log(1)
後,會先去看微任務列隊,不斷提取到執行棧中直到微任務列隊為空,因此這邊會先執行 Promise
,然後才是setTimeout
。
1;
3;
4;
2;
中階題
console.log("begins");
setTimeout(() => {
console.log("setTimeout 1");
Promise.resolve().then(() => {
console.log("promise 1");
});
}, 0);
new Promise(function (resolve, reject) {
console.log("promise 2");
setTimeout(function () {
console.log("setTimeout 2");
resolve("resolve 1");
}, 0);
}).then((res) => {
console.log("dot then 1");
setTimeout(() => {
console.log(res);
}, 0);
});
在讀完上面的程式碼,你覺得實際執行後,會印出什麼呢? 答案如下,讓我們一起來分析。
- 程式碼執行後會依順序執行程式,所以這時會先印出
'begins'
- 接著遇到
setTimeout
會把它放到宏任務列隊;然後遇到new Promise
會先執行,印出'promise 2'
,然後又遇到一個setTimeout
所以把它放到宏任務列隊。 - 接著主線程又空了,所以去檢查宏任務列隊,執行列隊中的最先的那個
setTimeout
,這時印出'setTimeout 1'
,然後遇到Promise.resolve()
,把它放到微任務列隊。 - 因為宏任務每次只會執行第一個項目,所以這時會去看微任務列隊,發現裡面有第三步放入的
Promise.resolve()
所以印出'promise 1'
。 - 這時微任務列隊空了,所以回去看宏任務列隊,裡面有個第二步放的
setTimeout
,所以印出'setTimeout 2'
- 然後因為這邊呼叫了
resolve
所以進入到.then
於是印出'dot then 1'
- 以及再把
setTimeout
放到宏任務列隊,因為這時微任務列隊已經是空的,所以把宏任務列隊中的setTimeout
放到執行棧,然後執行console.log(res)
,因為剛剛第六步resolve
的值是resolve 1
,所以最後印出resolve 1
"begins";
"promise 2";
"setTimeout 1";
"promise 1";
"setTimeout 2";
"dot then 1";
"resolve 1";
進階題
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
這一題大家覺得會印出什麼呢? 跟上一題的差別是這題有 async
的語法。答案在下方,我們一樣一行行分析。
- 程式碼執行後會依順序執行程式,所以這時會先印出
'script start'
,接著把setTimeout
把它放到宏任務列隊 - 然後呼叫
async1
函式,印出'async1 start'
- 然後呼叫
await async2()
所以印出'async2'
。注意,await
後的程式碼會被放到微任務列隊,所以不會馬上印出'async1 end'
而是會把它放到微任務列隊 - 接著程式繼續執行,遇到
new Promise
先印出裡面的'promise 1'
- 然後呼叫
resolve
,把.then
的放到微任務列隊。程式繼續執行,印出'script end'
- 這時候執行棧空了,所以去檢查微任務列隊,先印出第三步放的
'async1 end'
- 因為微任務列隊會一路執行到沒東西,所以繼續看微任務列隊,發現裡面還有剛剛第四步驟放入的
resolve
程式碼,所以印出'promise2'
- 這時微任務列隊空了,去看宏任務列隊,有第一步放入宏任務列隊的
setTimeout
所以把它印出
"script start";
"async1 start";
"async2";
"promise1";
"script end";
"async1 end";
"promise2";
"setTimeout";
小結
希望透過以上三個題目,大家對於事件循環的判讀題有更高的掌握!