26届秋招滴滴校招前端一面面经(2)

37 阅读3分钟

今天继续写在滴滴业务中台前端一面中遇到的事件循环题

题目代码

// 第一个 Promise 链
new Promise((resolve, reject) => {
  console.log(1);  // 同步输出
  resolve();        // 解决 Promise
  console.log(2);  // 同步输出
})
.then(() => {
  console.log(3);  // 微任务
})
.then(() => {
  console.log(4);  // 微任务
  setTimeout(() => {  // 宏任务
    console.log(5);  // 宏任务内同步输出
    new Promise((resolve) => {
      resolve();
    })
    .then(() => {
      console.log(6);  // 宏任务内微任务
    })
    .then(() => {
      console.log(7);  // 宏任务内微任务链
    });
  }, 0);  // 延迟设为 0
});

// 第二个独立的 Promise 链
new Promise((resolve) => {
  resolve();
})
.then(() => {
  console.log(8);  // 微任务
})
.then(() => {
  console.log(9);  // 微任务
});

执行结果

代码执行后,控制台输出顺序为: 1, 2, 3, 8, 4, 9, 5, 6, 7

分析

JavaScript 的事件循环(Event Loop)机制决定了代码的执行顺序,主要分为同步任务、微任务(Microtask)和宏任务(Macrotask)。以下是逐步分析:

  1. 同步代码执行(立即执行):

    • 执行第一个 new Promise的构造函数:输出 1,调用 resolve(),然后输出 2
    • 顺序输出:1, 2
  2. 微任务队列处理(同步代码执行后,立即处理所有微任务):

    • 第一个 Promise 的 resolve()将第一个 then回调(输出 3)加入微任务队列。

    • 第二个 Promise 的 resolve()将第一个 then回调(输出 8)加入微任务队列。

    • 此时微任务队列:[输出3, 输出8]

    • 处理微任务队列:

      • 执行输出 3:输出 3。执行后,第一个 Promise 链的下一个 then(输出 4)被加入微任务队列。队列变为:[输出8, 输出4]
      • 执行输出 8:输出 8。执行后,第二个 Promise 链的下一个 then(输出 9)被加入微任务队列。队列变为:[输出4, 输出9]
      • 执行输出 4:输出 4。在执行中,遇到 setTimeout,将其回调函数(包含输出 5等)加入宏任务队列。执行后,无新微任务。队列变为:[输出9]
      • 执行输出 9:输出 9
    • 微任务队列清空。

  3. 宏任务队列处理(微任务队列空后,处理宏任务):

    • 执行 setTimeout的回调函数(延迟为 0,但需等待微任务完成):

      • 输出 5
      • 内部新 Promise 的 resolve()then回调(输出 6)加入微任务队列。
    • 宏任务执行后,再次处理微任务队列:

      • 微任务队列:[输出6]
      • 执行输出 6:输出 6。执行后,内部 Promise 链的下一个 then(输出 7)加入微任务队列。
      • 执行输出 7:输出 7
    • 微任务队列清空。

  4. 最终输出顺序: 同步任务 → 微任务(按添加顺序)→ 宏任务 → 宏任务内产生的微任务。 因此顺序为:1, 2, 3, 8, 4, 9, 5, 6, 7

关键点说明:

  • 同步优先console.log(1)console.log(2)立即执行。
  • 微任务顺序:微任务(如 Promise then回调)在同步代码后、宏任务前执行,且队列按 FIFO 处理。第一个 Promise 的 then先加入队列,但第二个 Promise 的 then也在同步阶段加入,因此输出 3后输出 8
  • 宏任务延迟setTimeout即使延迟为 0,也需等待微任务队列清空后才执行。
  • 嵌套异步:宏任务(setTimeout)内产生的微任务,需在宏任务执行后立即处理。