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

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

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('后续代码');
  });
}

五、总结

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

相关推荐
Hello--_--World6 分钟前
DOM事件流与事件委托、判断数据类型、深浅拷贝、对象遍历方式
前端·javascript
野生技术架构师26 分钟前
2026年Java面试题集锦(含答案)
java·开发语言·面试
Ruihong35 分钟前
你的 Vue 3 TS 类型声明,VuReact 会处理成什么样的 React?
vue.js·react.js·面试
霪霖笙箫36 分钟前
「JS全栈AI学习」九、Multi-Agent 系统设计:架构与编排
前端·面试·全栈
threelab1 小时前
Vue3 + Trilab:打造高扩展性三维可视化插件化框架实战指南
javascript·3d·webgl
阿正的梦工坊1 小时前
JavaScript 函数作用域详解——为什么函数外面访问不到里面的变量?
开发语言·javascript
黑臂麒麟1 小时前
React Hooks 闭包陷阱:状态“丢失“的经典坑
javascript·react native·react.js·ecmascript
1314lay_10071 小时前
Vue+C#根据配置文件实现动态构建查询条件和动态表格
javascript·vue.js·elementui·c#
SuperEugene1 小时前
Vue3 前端配置驱动避坑:配置冗余、渲染性能、扩展性问题解决|配置驱动开发实战篇
前端·javascript·vue.js·驱动开发·前端框架
gCode Teacher 格码致知1 小时前
Javascript提高:Math.round 详解-由Deepseek产生
开发语言·javascript