28. “异步题大汇总”:输出顺序、陷阱解析 副本.md

查看飞书原文 ↗4,398 字符

1. 核心概念 (Core Concept)

前端异步题的核心考察点在于对 JavaScript 事件循环 (Event Loop) 机制的理解,特别是任务队列 (Task Queue / MacroTask Queue) 和微任务队列 (MicroTask Queue) 的执行顺序和时机。这些题目通常结合 setTimeout、setInterval、Promise、async/await、process.nextTick (Node.js)、queueMicrotask 等异步 API,要求判断代码的实际执行顺序和输出结果。

2. 为什么需要它? (The "Why")

3. API 与用法 (API & Usage)

核心涉及的异步 API 及其在事件循环中的典型归类:

事件循环基本流程(简化):

  1. 执行当前宏任务(如执行主脚本)。
  2. 检查微任务队列,执行所有微任务直到队列清空。
  3. 如有 UI 渲染或其他任务,执行之。
  4. 检查任务队列(宏任务队列),取出队列中的第一个宏任务执行。
  5. 返回步骤 2,循环往复。

经典代码示例 (来自 MDN 和 Promise/A+ 规范意译):

console.log('Start');

setTimeout(() => {
  console.log('setTimeout 1');
  Promise.resolve().then(() => {
    console.log('Promise in setTimeout');
  });
}, 0);

Promise.resolve().then(() => {
  console.log('Promise 1');
  setTimeout(() => {
    console.log('setTimeout in Promise');
  }, 0);
}).then(() => {
  console.log('Promise 2');
});

console.log('End');

// 预期输出 (浏览器环境,Node.js 11+):
// Start
// End
// Promise 1
// Promise 2
// setTimeout 1
// Promise in setTimeout
// setTimeout in Promise

解释:

  1. 执行主脚本,打印 'Start' 和 'End'。这是第一个宏任务。
  2. setTimeout 的回调被放入任务队列。
  3. 第一个 Promise.then 的回调被放入微任务队列。
  4. 第一个宏任务执行完毕。
  5. 检查微任务队列,执行第一个微任务,打印 'Promise 1'。在此微任务中,又创建一个新的 setTimeout,其回调被放入任务队列。
  6. 微任务队列非空,执行第二个微任务 (由 .then().then() 链式调用产生),打印 'Promise 2'。
  7. 微任务队列清空。
  8. 进入下一个事件循环迭代,从任务队列中取出第一个宏任务(第一个 setTimeout 的回调),执行它,打印 'setTimeout 1'。
  9. 在该宏任务中,创建一个 Promise.resolve().then,其回调被放入微任务队列。
  10. 当前宏任务执行完毕。
  11. 检查微任务队列,发现有新的微任务,执行它,打印 'Promise in setTimeout'。
  12. 微任务队列清空。
  13. 进入下一个事件循环迭代,从任务队列中取出下一个宏任务(在 Promise 回调中创建的 setTimeout),执行它,打印 'setTimeout in Promise'。
  14. 任务队列和微任务队列都空了。

4. 关键注意事项 (Key Considerations)**

5. 参考资料 (References)



原文链接与附件索引