My Personal Blog

Published on

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

Authors
  • avatar
    Name
    Tian Haipeng

基础题

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 1'
  5. 微任务队列空后,返回宏任务队列,执行第二个 setTimeout,打印 'setTimeout 2'
  6. 调用 resolve 后,进入 .then,打印 'dot then 1',并将最后的 setTimeout 放入宏任务队列。
  7. 最后宏任务队列执行,打印 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',调用 await async2(),打印 'async2',注意 await 后的代码会被放入微任务队列,因此不会立即打印 'async1 end'
  3. 遇到 new Promise,先打印 'promise1',调用 resolve 后,将 .then 放入微任务队列。
  4. 最后打印 'script end'
  5. 执行微任务队列,先打印 'async1 end',然后打印 'promise2'
  6. 微任务队列空了后,返回宏任务队列,执行 setTimeout,打印 'setTimeout'
"script start";
"async1 start";
"async2";
"promise1";
"script end";
"async1 end";
"promise2";
"setTimeout";

总结

通过以上三道题目,希望大家对事件循环的理解更加透彻!