🎯 一、为什么要手写 Event Loop?
- 大厂面试必问:async/await、Promise、setTimeout 混合输出顺序
- 源码能力提升:搞懂浏览器/Node.js 的任务调度机制
- 理解 JS 单线程异步执行本质,为后续自己写任务队列打下基础
🧠 二、Event Loop 核心概念速查
术语 | 解释 |
---|---|
Call Stack | 调用栈,JS 执行同步任务的地方 |
Macro Task | 宏任务,如 setTimeout/setInterval/I/O |
Micro Task | 微任务,如 Promise.then/MutationObserver |
Event Loop | 事件循环,调度所有任务的"引擎" |
- 执行顺序:主线程同步任务 → 微任务队列 → 宏任务队列
- 每次执行完一个宏任务后,立即清空所有微任务
🔍 三、常考面试题(输出顺序)
javascript
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
输出顺序? 1、4、3、2
✍️ 四、核心思想:模拟微/宏任务队列
- 维护两个队列:
macroQueue
和microQueue
- 每次执行一个 macro 后,依次执行 microQueue 直到清空
- 采用递归或循环驱动任务调度
📦 五、简易 Event Loop 模拟器实现
kotlin
class EventLoop {
constructor() {
this.macroQueue = [];
this.microQueue = [];
this.running = false;
}
// 加入宏任务
setMacroTask(fn) {
this.macroQueue.push(fn);
this.run();
}
// 加入微任务
setMicroTask(fn) {
this.microQueue.push(fn);
this.run();
}
run() {
if (this.running) return;
this.running = true;
while (this.macroQueue.length > 0) {
// 执行一个宏任务
const macro = this.macroQueue.shift();
macro();
// 清空微任务队列
while (this.microQueue.length > 0) {
const micro = this.microQueue.shift();
micro();
}
}
this.running = false;
}
}
✅ 六、使用示例(验证调度顺序)
arduino
const loop = new EventLoop();
console.log(1);
loop.setMacroTask(() => {
console.log(2);
});
loop.setMicroTask(() => {
console.log(3);
});
console.log(4);
// 输出:1 4 3 2
🔁 七、进阶模拟 async/await
async/await 本质就是自动拆分为同步 + 微任务
javascript
async function test() {
console.log('a');
await Promise.resolve();
console.log('b');
}
test();
console.log('c');
// 输出:a c b
await
前为同步,await
后回到微任务
❗ 八、面试陷阱总结
题型 | 易错点 |
---|---|
setTimeout + Promise | 谁先输出? Promise 微任务先于 setTimeout 宏任务 |
多重微任务 | 一次性全部执行,直到清空 microQueue |
多层嵌套 | 推理需模拟"每次大循环后立即清 micro" |