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