一位学长面试的时候遇到的题目
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️⃣ 微任务
执行顺序详解:
-
同步阶段:
- 执行
async1()
输出 E - 执行
async2()
输出 G - 执行 Promise 构造函数输出 I
- 执行
-
微任务队列:
await async2()
后的代码 → 输出 F (注意)- Promise.then → 输出 J
-
宏任务队列:
- setTimeout 回调 → 输出 H
关键机制解析:
javascript
// async/await 本质是 Promise 语法糖
async function example() {
await somePromise;
console.log('后续代码');
}
// 等价于
function example() {
return somePromise.then(() => {
console.log('后续代码');
});
}
五、总结
希望下次我遇到这种题目不会陷入死一样的沉默😅