🚀 事件循环在JavaScript中的作用和工作原理
欢迎来到我的博客文章!所有文章都是满满的前端干货,文章简明扼要。
JavaScript 是单线程语言,但通过 事件循环(Event Loop) 机制实现了高效的异步非阻塞 I/O 操作。这是 JS 能在浏览器和 Node.js 中处理大量并发任务(如网络请求、定时器、用户交互等)的核心原理。
一、事件循环的作用
- 协调同步与异步任务的执行顺序
- 避免主线程被长时间阻塞
- 确保回调函数在合适的时机被调用
💡 简单说:事件循环让 JavaScript "看起来"能同时做多件事,尽管它只有一个主线程。
二、核心组成部分
要理解事件循环,需先了解以下概念:
| 组件 | 说明 |
| 调用栈(Call Stack) | 存放当前正在执行的函数(同步代码),后进先出(LIFO) |
| 任务队列(Task Queue / Macro Task Queue) | 存放宏任务(如 setTimeout、setInterval、I/O、UI 渲染等)的回调 |
| 微任务队列(Microtask Queue) | 存放微任务(如 Promise.then/catch/finally、queueMicrotask、MutationObserver)的回调 |
| 事件循环(Event Loop) | 不断检查调用栈是否为空,若空则从队列中取任务执行 |
三、工作原理(简化流程)
- 执行全局同步代码,压入调用栈。
- 遇到异步操作(如 setTimeout、fetch、Promise):
- 宏任务(如 setTimeout)的回调放入 宏任务队列
- 微任务(如 Promise.then)的回调放入 微任务队列
- 同步代码执行完毕,调用栈清空。
- 事件循环开始工作:
- 先清空微任务队列(全部执行完)
- 再取一个宏任务执行(注意:一次只取一个!)
- 重复上述过程。
✅ 关键规则:每次宏任务执行后,会立即执行所有可运行的微任务。
四、宏任务 vs 微任务
| 类型 | 常见来源 |
| 宏任务(Macro Task) | setTimeout, setInterval, setImmediate(Node.js), I/O, UI 渲染(浏览器) |
| 微任务(Microtask) | Promise.then/catch/finally, queueMicrotask, MutationObserver, process.nextTick(Node.js,优先级更高) |
⚠️ 注意:process.nextTick 在 Node.js 中比 Promise 微任务优先级更高,但在浏览器中不存在。
五、经典示例分析
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
输出顺序:1 4 3 2 解释: - 同步代码 1 和 4 先执行。
- setTimeout 回调进入宏任务队列。
- Promise.then 进入微任务队列。
- 同步代码结束 → 执行所有微任务(输出 3)→ 执行下一个宏任务(输出 2)。
六、浏览器中的渲染时机
在浏览器中,UI 渲染也是一个宏任务。事件循环在执行完一个宏任务 + 所有微任务后,可能进行一次页面重绘(如果需要)。
因此:
- 频繁 DOM 操作应尽量批量进行,避免触发过多重排重绘。
- requestAnimationFrame 会在下一次重绘前执行,常用于动画。