JavaScript事件循环机制
JavaScript 事件循环机制(Event Loop)详解
JavaScript 是 单线程、非阻塞 语言,依赖 事件循环(Event Loop) 来实现异步编程。它的执行模型包括 调用栈(Call Stack)、任务队列(Task Queue)和微任务队列(Microtask Queue)。
1. 事件循环(Event Loop)基本流程
事件循环的核心工作方式:
- 同步任务(Synchronous) 进入 调用栈 依次执行。
- 遇到 异步任务(如 setTimeout、Promise、I/O 操作),交给 Web API(如浏览器、Node.js 运行时)处理,并继续执行同步代码。
- 同步代码执行完毕,调用栈清空,事件循环检查 微任务队列(Microtask Queue),依次执行所有微任务。
- 微任务执行完毕,进入 宏任务队列(Macro Task Queue),取出第一个任务执行。
- 重复以上步骤。
2. 任务类型:同步任务 vs 异步任务
JavaScript 任务分为:
(1) 同步任务(Synchronous)
- 直接执行,放入 调用栈(Call Stack)
- 例子:
console.log('同步任务1'); // 立即执行
(2) 异步任务(Asynchronous)
- 由 Web API 处理,待合适时机进入任务队列:
- 宏任务(Macro Task)
- 微任务(Micro Task)
3. 宏任务(Macro Task) vs 微任务(Micro Task)
任务类型 | 常见API | 进入队列 |
---|---|---|
宏任务(Macro Task) | setTimeout 、setInterval 、setImmediate (Node.js)、I/O 、UI渲染 | 任务队列(Task Queue) |
微任务(Micro Task) | Promise.then() 、queueMicrotask() 、MutationObserver 、process.nextTick() (Node.js) | 微任务队列(Microtask Queue) |
4. 事件循环执行流程
console.log('同步任务1');
setTimeout(() => {
console.log('宏任务1');
}, 0);
Promise.resolve().then(() => {
console.log('微任务1');
});
console.log('同步任务2');
执行顺序解析
console.log('同步任务1')
执行(同步任务)setTimeout()
放入 宏任务队列,等待执行Promise.then()
放入 微任务队列console.log('同步任务2')
执行(同步任务)- 调用栈清空,检查 微任务队列,执行
console.log('微任务1')
- 微任务清空后,执行 宏任务队列,输出
console.log('宏任务1')
最终输出顺序
同步任务1
同步任务2
微任务1
宏任务1
5. setTimeout(fn, 0)
为什么不立即执行?
setTimeout(fn, 0)
也会进入 宏任务队列,需要等当前同步任务执行完毕,并在 微任务全部执行完毕后,才能执行。- 示例
执行顺序setTimeout(() => console.log('宏任务'), 0); Promise.resolve().then(() => console.log('微任务')); console.log('同步任务');
同步任务 微任务 宏任务
6. Promise
和 setTimeout
谁先执行?
Promise.then()
是微任务,会先执行setTimeout()
是宏任务,等微任务执行完才执行
示例
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
执行顺序
Promise
setTimeout
7. 事件循环完整示例
console.log('start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise1');
}).then(() => {
console.log('Promise2');
});
console.log('end');
执行顺序
console.log('start')
(同步任务)setTimeout()
进入 宏任务队列Promise.then()
进入 微任务队列console.log('end')
(同步任务)- 同步任务结束,执行微任务
Promise1
Promise2
- 微任务执行完毕,执行宏任务
setTimeout
最终输出:
start
end
Promise1
Promise2
setTimeout
8. async/await
也是微任务
示例
async function asyncFunc() {
console.log('A');
await Promise.resolve();
console.log('B');
}
console.log('C');
asyncFunc();
console.log('D');
执行顺序
C
A
D
B
解释
console.log('C')
(同步任务)- 调用
asyncFunc()
,输出A
await Promise.resolve()
让console.log('B')
进入 微任务队列console.log('D')
(同步任务)- 执行微任务
console.log('B')
9. setTimeout()
和 setImmediate()
(Node.js)
在 Node.js 中:
setTimeout(fn, 0)
进入 定时器队列setImmediate(fn)
进入 Check 队列setImmediate()
通常比setTimeout(0)
先执行
示例
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
输出顺序(Node.js)
setImmediate
setTimeout
10. 关键点总结
概念 | 说明 |
---|---|
同步任务 | 立即执行,进入 调用栈 |
异步任务 | 由 Web API 处理,稍后执行 |
宏任务(Macro Task) | setTimeout 、setInterval 、setImmediate 、I/O |
微任务(Micro Task) | Promise.then() 、queueMicrotask() 、MutationObserver |
事件循环(Event Loop) | 先执行同步任务 → 再执行微任务 → 再执行宏任务 |
11. 最佳实践
✅ 避免阻塞主线程
- 使用
setTimeout(fn, 0)
或requestIdleCallback(fn)
处理密集计算
✅ 优先使用微任务优化异步流程
Promise.then()
比setTimeout()
先执行
✅ 了解 async/await
也是微任务
await
后的代码会在微任务队列中执行
总结
- JavaScript 是单线程,使用事件循环管理异步任务
- 任务分为:
- 同步任务(调用栈直接执行)
- 异步任务(进入宏任务/微任务队列)
- 执行顺序:
- 先执行同步任务
- 再执行所有微任务
- 最后执行宏任务
Promise.then()
比setTimeout()
先执行async/await
本质上是 Promise,属于微任务
这些概念对于理解 JavaScript 的异步执行至关重要!🚀