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

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

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

五、总结

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

相关推荐
2601_949950633 小时前
练题簿在线免费刷题 从资料整理到考前自测
面试·职场和发展·pdf·word·刷题
福兮说5 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
时间的拾荒人5 小时前
Qt 多线程详解:从 QThread 到实战
开发语言·qt·面试
海绵宝宝转agent5 小时前
MySql高频面试八股开源笔记总结
mysql·面试·开源
小呆呆6666 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
凤城老人6 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)6 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
lsc_blog7 小时前
远程和居家办公的经历,技术简历上怎么摆
面试·职场和发展·重构·pdf·求职招聘
码艺-Alimjan7 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_7 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线