面试官:讲一下输出顺序🙃

一位学长面试的时候遇到的题目

JavaScript 的事件循环(event loop)是一种机制,它负责执行异步代码并管理调用栈任务队列 之间的交互,确保异步操作如定时器、 promises 和 I/O 操作能够在适当的时候被执行。简而言之,事件循环使得 JavaScript 能够处理并发操作,而无需多线程。

一、核心机制

  • 同步优先原则:主线程同步代码立即执行
  • 微任务优先原则:所有微任务(Promise、queueMicrotask)会在当前宏任务结束后立即执行
  • 渲染时机:每完成一个宏任务后可能触发页面渲染
  • 任务队列管理:宏任务队列(setTimeout等)每次只取一个任务执行

二、异步任务类型

宏任务(MacroTask)

类型 触发场景 执行优先级 所属环境 关键特性
script 整体代码 页面加载/脚本执行 最高 浏览器 首个默认执行的宏任务
setTimeout 定时器到期 通用 最小延迟4ms(浏览器规范)
setInterval 周期性定时器 通用 可能堆积任务
I/O 操作 文件读写、网络请求(fetch/XHR) 通用 回调进入任务队列
DOM 事件 click/keydown 等交互事件 浏览器 事件触发时入队
requestAnimationFrame 动画帧回调 中高 浏览器 在渲染前执行
MessageChannel 跨文档通信 浏览器 优先级高于 setTimeout
setImmediate Node.js 立即执行 Node.js 比 setTimeout(0) 更快
  • 宏任务执行特点: 每个宏任务执行后都会重新检查微任务队列
javascript 复制代码
// 浏览器环境示例
setTimeout(() => console.log('Timeout1'), 0);
setTimeout(() => {
  console.log('Timeout2');
  setTimeout(() => console.log('Nested Timeout'), 0);
}, 0);

// 输出顺序:Timeout1 → Timeout2 → Nested Timeout

微任务(MicroTask)

类型 触发机制 执行优先级 所属环境 关键特性
Promise.then Promise 状态变更(resolve/reject) 通用 链式调用形成微任务队列
queueMicrotask 直接调用 通用 官方推荐的微任务API
MutationObserver DOM 变更观测 浏览器 替代已废弃的 Mutation Events
process.nextTick Node.js 阶段切换 极高 Node.js 优先于所有微任务
  • 微任务执行特点: 微任务队列必须完全清空才会执行下一个宏任务
javascript 复制代码
// 微任务嵌套示例
Promise.resolve().then(() => {
  console.log('Microtask1');
  queueMicrotask(() => console.log('Nested Microtask'));
});

Promise.resolve().then(() => console.log('Microtask2'));

/* 执行顺序:
Microtask1 → Microtask2 → Nested Microtask
 */

三、原理实践

javascript 复制代码
console.log('同步任务开始');

setTimeout(() => {
  console.log('宏任务1启动');
  Promise.resolve().then(() => {
    console.log('宏任务1的微任务');
    queueMicrotask(() => console.log('嵌套微任务'));
  });
});

setTimeout(() => console.log('宏任务2'));

Promise.resolve().then(() => console.log('初始微任务'));

console.log('同步任务结束');

/* 执行结果:
同步任务开始
同步任务结束
初始微任务        ← 第一个宏任务(script)后的微任务
宏任务1启动
宏任务1的微任务   ← 第二个宏任务后的第一层微任务
嵌套微任务        ← 第二层嵌套微任务
宏任务2          ← 第三个宏任务 
*/

四、面试题解析

javascript 复制代码
async function async1() {
  console.log('E'); // 1️⃣ 同步执行
  await async2();   // 2️⃣ 触发微任务
  console.log('F'); // 4️⃣ 微任务执行
}

async function async2() {
  console.log('G'); // 3️⃣ 同步执行
}

setTimeout(() => console.log('H'), 0); // 6️⃣ 宏任务

async1(); // 触发执行流

new Promise((res) => {
  console.log('I'); // 5️⃣ 同步执行
  res();
}).then(() => console.log('J')); // 5️⃣ 微任务

执行顺序详解:

  1. 同步阶段:

    • 执行 async1() 输出 E
    • 执行 async2() 输出 G
    • 执行 Promise 构造函数输出 I
  2. 微任务队列:

    • await async2() 后的代码 → 输出 F注意
    • Promise.then → 输出 J
  3. 宏任务队列:

    • setTimeout 回调 → 输出 H

关键机制解析:

javascript 复制代码
// async/await 本质是 Promise 语法糖
async function example() {
  await somePromise;
  console.log('后续代码');
}

// 等价于
function example() {
  return somePromise.then(() => {
    console.log('后续代码');
  });
}

五、总结

希望下次我遇到这种题目不会陷入死一样的沉默😅

相关推荐
JieE2122 小时前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
冬奇Lab4 小时前
AI Workflow 定义的四次演进:从 Markdown 到 JS 脚本,再到分布式多 Agent
javascript·人工智能·agent
一颗烂土豆10 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
kyriewen12 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
weedsfly15 小时前
迭代器、生成器与异步迭代——让数据“按需流动”的艺术
前端·javascript
假如让我当三天老蒯15 小时前
前端跨域解决方案(学习用)
前端·javascript·面试
Colin草率地做慢慢地改15 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构
铁皮饭盒17 小时前
Bun 哪比 Node.js 快?
javascript·后端
JieE2121 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
candyTong1 天前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构