最常见的事件循环 (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";
小结
希望透过以上三个题目,大家对于事件循环的判读题有更高的掌握!