最常见的事件循环 (Event Loop) 面试题目汇整

2023年1月26日

💎 加入 E+ 成長計畫 與超過 500+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

事件循环(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);
});

在读完上面的代码,你觉得实际执行后,会印出什么呢? 答案如下,让我们一起来分析。

  1. 代码执行后会依顺序执行程式,所以这时会先印出 'begins'
  2. 接着遇到setTimeout 会把它放到宏任务列队;然后遇到new Promise 会先执行,印出'promise 2' ,然后又遇到一个setTimeout 所以把它放到宏任务列队。
  3. 接着主线程又空了,所以去检查宏任务列队,执行列队中的最先的那个setTimeout,这时印出'setTimeout 1' ,然后遇到Promise.resolve() ,把它放到微任务列队。
  4. 因为宏任务每次只会执行第一个项目,所以这时会去看微任务列队,发现里面有第三步放入的 Promise.resolve() 所以印出 'promise 1'
  5. 这时微任务列队空了,所以回去看宏任务列队,里面有个第二步放的 setTimeout ,所以印出 'setTimeout 2'
  6. 然后因为这边呼叫了 resolve 所以进入到 .then 于是印出 'dot then 1'
  7. 以及再把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 的语法。答案在下方,我们一样一行行分析。

  1. 代码执行后会依顺序执行程式,所以这时会先印出 'script start',接着把setTimeout 把它放到宏任务列队
  2. 然后呼叫 async1 函式,印出 'async1 start'
  3. 然后呼叫 await async2() 所以印出 'async2'。注意, await 后的代码会被放到微任务列队,所以不会马上印出 'async1 end' 而是会把它放到微任务列队
  4. 接着程式继续执行,遇到 new Promise 先印出里面的 'promise 1'
  5. 然后呼叫 resolve ,把 .then 的放到微任务列队。程式继续执行,印出 'script end'
  6. 这时候执行栈空了,所以去检查微任务列队,先印出第三步放的 'async1 end'
  7. 因为微任务列队会一路执行到没东西,所以继续看微任务列队,发现里面还有刚刚第四步骤放入的 resolve 代码,所以印出 'promise2'
  8. 这时微任务列队空了,去看宏任务列队,有第一步放入宏任务列队的 setTimeout 所以把它印出
"script start";
"async1 start";
"async2";
"promise1";
"script end";
"async1 end";
"promise2";
"setTimeout";

小结

希望透过以上三个题目,大家对于事件循环的判读题有更高的掌握!

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們