深入理解JavaScript事件循环:从宏任务到微任务
【摘要】 深入理解JavaScript事件循环:从宏任务到微任务本文深入剖析JavaScript事件循环机制,帮助你理解异步编程的核心原理,掌握宏任务与微任务的执行顺序。 为什么需要事件循环?JavaScript是单线程语言,但能处理大量并发请求,这背后全靠事件循环机制。理解事件循环,是写出高质量异步代码的基础。 执行栈与调用栈JavaScript引擎使用调用栈(Call Stack)来跟踪函数执行...
深入理解JavaScript事件循环:从宏任务到微任务
本文深入剖析JavaScript事件循环机制,帮助你理解异步编程的核心原理,掌握宏任务与微任务的执行顺序。
为什么需要事件循环?
JavaScript是单线程语言,但能处理大量并发请求,这背后全靠事件循环机制。理解事件循环,是写出高质量异步代码的基础。
执行栈与调用栈
JavaScript引擎使用调用栈(Call Stack)来跟踪函数执行:
function multiply(a, b) {
return a * b;
}
function square(n) {
return multiply(n, n);
}
console.log(square(5));
// 执行顺序:square(5) -> multiply(5, 5) -> console.log(25)
调用栈遵循"后进先出"原则,函数调用会压栈,执行完毕会出栈。
浏览器环境的事件循环
宏任务(MacroTask)与微任务(MicroTask)
事件循环将任务分为两类:
| 类型 | 示例 |
|---|---|
| 宏任务 | setTimeout、setInterval、setImmediate(Node.js)、I/O、UI渲染 |
| 微任务 | Promise.then/catch/finally、MutationObserver、queueMicrotask、process.nextTick(Node.js) |
执行顺序
1. 执行一个宏任务(如整体代码)
2. 执行过程中产生的所有微任务,全部清空
3. 渲染(浏览器)
4. 取出下一个宏任务
5. 重复2-4步
实战代码分析
console.log('start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise1');
}).then(() => {
console.log('promise2');
});
console.log('end');
// 输出顺序:
// start -> end -> promise1 -> promise2 -> setTimeout
分析过程:
- 同步代码执行:
start、end setTimeout回调放入宏任务队列- 两个Promise回调放入微任务队列
- 清空微任务队列:
promise1、promise2 - 执行下一个宏任务:
setTimeout
Node.js环境的事件循环
Node.js的事件循环与浏览器略有不同,分为6个阶段:
┌───────────────────────┐
│ timers │ ← 执行setTimeout/setInterval回调
└───────────────────────┘
↓
┌───────────────────────┐
│ pending callbacks │ ← 执行I/O回调(某些系统级操作)
└───────────────────────┘
↓
┌───────────────────────┐
│ idle/prepare │ ← 内部使用
└───────────────────────┘
↓
┌───────────────────────┐
│ poll │ ← 获取新的I/O事件,执行I/O回调
└───────────────────────┘
↓
┌───────────────────────┐
│ check │ ← 执行setImmediate回调
└───────────────────────┘
↓
┌───────────────────────┐
│ closing callbacks │ ← 执行关闭事件回调(如socket.on('close'))
└───────────────────────┘
process.nextTick的特殊性
setTimeout(() => {
console.log('timeout');
}, 0);
process.nextTick(() => {
console.log('nextTick');
});
Promise.resolve().then(() => {
console.log('promise');
});
// 输出:nextTick -> promise -> timeout
process.nextTick优先级高于Promise微任务,它在当前操作完成后、事件循环继续前执行。
常见陷阱与最佳实践
陷阱1:阻塞事件循环
// ❌ 错误:阻塞事件循环
function heavyComputation() {
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i;
}
return result;
}
// ✅ 正确:使用Web Worker或拆分任务
async function processInChunks(data, chunkSize = 1000) {
for (let i = 0; i < data.length; i += chunkSize) {
await processChunk(data.slice(i, i + chunkSize));
await new Promise(resolve => setTimeout(resolve, 0));
}
}
陷阱2:微任务饥饿
// ❌ 错误:无限递归微任务,阻塞事件循环
function microtaskRecursion() {
Promise.resolve().then(() => {
console.log('microtask');
microtaskRecursion();
});
}
// ✅ 正确:限制微任务数量或使用宏任务
最佳实践
- 避免在微任务中执行耗时操作
- 合理使用
queueMicrotask - 理解
process.nextTick与setImmediate的区别 - 使用
async/await简化异步代码
实战:手写事件循环模拟
class EventLoop {
constructor() {
this.macrotaskQueue = [];
this.microtaskQueue = [];
}
// 添加宏任务
setTimeout(callback) {
this.macrotaskQueue.push(callback);
}
// 添加微任务
queueMicrotask(callback) {
this.microtaskQueue.push(callback);
}
// 执行事件循环
run() {
while (this.macrotaskQueue.length > 0 || this.microtaskQueue.length > 0) {
// 执行一个宏任务
if (this.macrotaskQueue.length > 0) {
const macroTask = this.macrotaskQueue.shift();
macroTask();
}
// 清空所有微任务
while (this.microtaskQueue.length > 0) {
const microTask = this.microtaskQueue.shift();
microTask();
}
}
}
}
// 使用示例
const loop = new EventLoop();
loop.setTimeout(() => console.log('macro1'));
loop.queueMicrotask(() => console.log('micro1'));
loop.queueMicrotask(() => console.log('micro2'));
loop.run();
// 输出:micro1 -> micro2 -> macro1
总结
| 概念 | 说明 |
|---|---|
| 宏任务 | setTimeout、setInterval、I/O、UI渲染 |
| 微任务 | Promise、MutationObserver、queueMicrotask |
| 执行顺序 | 宏任务 -> 清空微任务 -> 渲染 -> 下一个宏任务 |
| Node.js | 6个阶段,process.nextTick优先级最高 |
掌握事件循环,是理解JavaScript异步编程的关键。在实际开发中,合理利用宏任务和微任务,可以写出更高效、更可维护的代码。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)