常见的事件循环面试题目整理

251 阅读3分钟

基础题

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";