# 事件循环:宏任务与微任务的执行顺序
## 引言
在JavaScript的世界中,事件循环(Event Loop)是理解异步编程的核心机制。很多开发者虽然经常使用Promise、setTimeout等异步API,但对于宏任务(macro task)与微任务(micro task)的执行顺序仍然存在疑惑。本文将深入剖析事件循环的工作原理,帮您彻底掌握异步代码的执行顺序。
## 一、什么是事件循环?
JavaScript是单线程语言,这意味着它一次只能执行一个任务。为了处理异步操作,JavaScript使用事件循环机制来协调各种任务的执行。
事件循环的基本流程:
1. 执行当前同步代码(属于宏任务)
2. 执行当前所有微任务
3. 执行渲染(如果需要)
4. 从宏任务队列中取出下一个任务执行
5. 重复上述过程
## 二、宏任务与微任务分类
### 宏任务(Macro Task)包括:
- script整体代码
- setTimeout/setInterval
- I/O操作
- UI渲染
- setImmediate(Node.js环境)
- requestAnimationFrame(浏览器环境)
### 微任务(Micro Task)包括:
- Promise.then/catch/finally
- MutationObserver
- process.nextTick(Node.js环境)
- queueMicrotask
## 三、执行顺序的黄金法则
记住这个核心原则:**每次执行一个宏任务后,都会清空整个微任务队列**。
让我们用代码示例来说明:
```javascript
console.log('1. 同步代码 - 宏任务');
setTimeout(() => {
console.log('4. setTimeout - 宏任务');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise - 微任务');
});
console.log('2. 同步代码 - 宏任务');
```
输出顺序:
```
1. 同步代码 - 宏任务
2. 同步代码 - 宏任务
3. Promise - 微任务
4. setTimeout - 宏任务
```
## 四、复杂场景分析
让我们看一个更复杂的例子:
```javascript
console.log('脚本开始'); // 宏任务1
setTimeout(() => {
console.log('setTimeout'); // 宏任务2
Promise.resolve().then(() => {
console.log('setTimeout中的微任务'); // 微任务3
});
}, 0);
Promise.resolve().then(() => {
console.log('Promise1'); // 微任务1
Promise.resolve().then(() => {
console.log('Promise1中的微任务'); // 微任务2
});
});
console.log('脚本结束'); // 宏任务1
```
输出顺序:
```
脚本开始
脚本结束
Promise1
Promise1中的微任务
setTimeout
setTimeout中的微任务
```
## 五、常见面试题解析
### 面试题1:
```javascript
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => console.log('3'));
}, 0);
new Promise((resolve) => {
console.log('4');
resolve();
}).then(() => {
console.log('5');
});
console.log('6');
```
输出顺序:1, 4, 6, 5, 2, 3
### 面试题2:
```javascript
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
```
输出顺序:
script start, async1 start, async2, promise1, script end, async1 end, promise2, setTimeout
## 六、实际应用建议
1. **性能优化**:密集计算任务可以分解为多个微任务,避免阻塞UI渲染
2. **避免嵌套**:过多嵌套的Promise.then可能导致微任务队列过长
3. **合理使用**:动画优先使用requestAnimationFrame而非setTimeout
4. **错误处理**:在微任务中发生的错误不会阻止后续微任务执行
## 结语
理解事件循环中宏任务与微任务的执行顺序,是掌握JavaScript异步编程的关键。记住每次执行一个宏任务后都会清空整个微任务队列这一核心原则,就能轻松应对各种异步场景。
希望通过本文的讲解,您对事件循环有了更清晰的认识。在实际开发中遇到异步问题时,不妨回想一下宏任务与微任务的执行顺序,问题往往就能迎刃而解。