JavaScript中的事件循环与任务队列(Event Loop & Task Queues)
字数 1315 2025-11-12 18:43:10
JavaScript中的事件循环与任务队列(Event Loop & Task Queues)
1. 事件循环的基本概念
JavaScript是单线程语言,但需要处理异步操作(如定时器、网络请求、用户交互等)。事件循环(Event Loop)是协调同步任务和异步任务的机制,确保代码按预期顺序执行。
核心组成:
- 调用栈(Call Stack):执行同步代码,遵循后进先出(LIFO)原则。
- 任务队列(Task Queue):存放待执行的异步任务回调。
- 事件循环:监控调用栈和任务队列,当调用栈为空时,将任务队列中的任务推入栈中执行。
2. 任务队列的类型
任务队列分为两种优先级:
-
宏任务队列(MacroTask Queue):
- 包含:
setTimeout、setInterval、I/O操作、UI渲染、事件回调(如点击事件)。 - 特点:每次事件循环只执行一个宏任务。
- 包含:
-
微任务队列(MicroTask Queue):
- 包含:
Promise.then/catch/finally、MutationObserver、queueMicrotask。 - 特点:优先级高于宏任务,当调用栈清空后,会连续执行所有微任务,直到微任务队列为空。
- 包含:
3. 事件循环的执行流程
- 执行全局同步代码(属于宏任务),逐行压入调用栈。
- 遇到异步任务:
- 宏任务(如
setTimeout)将其回调函数加入宏任务队列。 - 微任务(如
Promise.then)将其回调加入微任务队列。
- 宏任务(如
- 同步代码执行完毕,调用栈为空。
- 检查微任务队列,依次执行所有微任务(包括微任务中产生的新的微任务)。
- 微任务队列清空后,执行下一个宏任务(从宏任务队列中取一个)。
- 循环步骤4-5,直到所有任务执行完毕。
4. 示例代码分析
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
执行步骤分解:
- 同步代码执行:
- 输出
Start setTimeout回调加入宏任务队列Promise.then回调加入微任务队列- 输出
End
- 输出
- 调用栈为空,检查微任务队列:
- 执行
Promise.then回调,输出Promise
- 执行
- 微任务队列清空,执行下一个宏任务:
- 输出
Timeout
- 输出
结果:
Start
End
Promise
Timeout
5. 复杂场景:嵌套任务
setTimeout(() => console.log("Timeout 1"), 0);
Promise.resolve().then(() => {
console.log("Promise 1");
setTimeout(() => console.log("Timeout 2"), 0);
});
Promise.resolve().then(() => console.log("Promise 2"));
执行顺序:
- 同步代码:将
setTimeout回调加入宏任务队列,两个Promise.then加入微任务队列。 - 清空微任务队列:
- 输出
Promise 1,将内部的setTimeout回调加入宏任务队列。 - 输出
Promise 2。
- 输出
- 执行宏任务队列:
- 第一个宏任务:输出
Timeout 1。 - 第二个宏任务(来自微任务):输出
Timeout 2。
- 第一个宏任务:输出
结果:
Promise 1
Promise 2
Timeout 1
Timeout 2
6. 注意事项
- 微任务优先级最高:即使在宏任务中产生的微任务,也会在当前宏任务结束后立即执行。
- 避免阻塞:长时间同步代码会阻塞事件循环,导致界面卡顿(例如循环内大量计算)。
- Web Workers:可分流复杂任务到后台线程,避免影响主线程事件循环。
通过理解事件循环的优先级和执行顺序,可以更精准地控制异步代码的逻辑,避免因执行顺序问题导致的Bug。