JavaScript事件循环与执行队列:从原理到复杂示例解析
一、什么是事件循环?
JavaScript是一门单线程语言,但通过事件循环(Event Loop)机制实现了异步非阻塞操作。事件循环的核心作用是协调调用栈(Call Stack)、消息队列(Message Queue)和微任务队列(Microtask Queue)的工作。
二、核心概念解析
1. 调用栈(Call Stack)
- 后进先出(LIFO)结构
- 用于存储同步代码的执行上下文
2. 任务队列(Task Queue)
-
宏任务队列(Macrotask Queue):
- setTimeout/setInterval
- I/O操作
- UI渲染
- script整体代码
-
微任务队列(Microtask Queue):
- Promise.then/catch/finally
- MutationObserver
- queueMicrotask
3. 事件循环运行流程
- 执行同步代码直至调用栈清空
- 执行所有微任务直至微任务队列清空
- 执行一个宏任务
- 重复步骤1-3
三、复杂示例演示
console.log('【1】同步代码开始');
setTimeout(() => {
console.log('【11】setTimeout宏任务');
new Promise(resolve => {
console.log('【12】Promise构造函数(同步)');
resolve();
}).then(() => {
console.log('【13】Promise微任务');
queueMicrotask(() => console.log('【14】嵌套微任务'));
});
}, 0);
Promise.resolve().then(() => {
console.log('【2】第一个微任务');
setTimeout(() => console.log('【3】微任务中触发的宏任务'), 0);
});
async function asyncFunc() {
console.log('【4】async函数同步代码');
await Promise.resolve();
console.log('【5】await后微任务');
setTimeout(() => console.log('【6】async中的宏任务'), 0);
}
asyncFunc();
new Promise(resolve => {
console.log('【7】Promise构造函数(同步)');
resolve();
}).then(() => {
console.log('【8】链式微任务1');
}).then(() => {
console.log('【9】链式微任务2');
});
console.log('【10】同步代码结束');
四、执行过程详细解析
执行顺序预测结果:
1 → 4 → 7 → 10 → 2 → 5 → 8 → 9 → 11 → 12 → 13 → 14 → 3 → 6
分步解析:
-
同步代码阶段
- 输出1、4、7、10(按代码顺序执行)
-
微任务队列处理
- 第一个Promise.then输出2
- async函数await后的代码输出5
- 链式Promise输出8和9
-
宏任务阶段
- 第一个setTimeout回调:
- 输出11和12(同步代码)
- Promise.then产生微任务13和14
- 继续处理嵌套微任务
- 第一个setTimeout回调:
-
嵌套任务处理
- 输出13后触发新的微任务14
- 最后执行各宏任务中的setTimeout输出3和6
五、关键结论
-
执行优先级:
- 同步代码 > 微任务 > 宏任务
- 同一队列中按入队顺序执行
-
重要特性:
- 微任务会在当前宏任务结束后立即执行
- 微任务中触发的微任务会继续在当前周期执行
- 每个宏任务结束后都会清空微任务队列
-
性能注意:
- 避免微任务无限嵌套
- 长时间同步代码会阻塞事件循环
六、应用场景
- 优化渲染性能时安排UI更新
- 处理高优先级异步操作
- 实现复杂的异步流程控制
这篇博客通过理论+实践的方式解析了JavaScript的事件循环机制,读者可以通过运行示例代码并对照解析,更直观地理解执行顺序。建议在浏览器控制台实际运行代码观察结果。