最常見的事件循環 (Event Loop) 面試題目彙整
2023年1月26日
💎 加入 E+ 成長計畫 與超過 800+ 位工程師一同在社群成長,並獲得更多深度的軟體前後端學習資源
事件循環 (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";
小結
希望透過以上三個題目,大家對於事件循環的判讀題有更高的掌握!