JavaScript事件循环是宿主环境实现的调度机制,非ECMAScript规范;其核心为调用栈清空后,依次执行一个宏任务、全部微任务、渲染阶段,循环往复。

JavaScript 事件循环不是“一个队列加一个循环”的简化模型,而是一套由调用栈、两类任务队列、渲染阶段和宿主环境协同驱动的运行时调度机制。它不属 ECMAScript 规范,而是浏览器或 Node.js 这类宿主环境实现的底层协调系统。
调用栈是事件循环的起点和标尺
所有同步代码都在调用栈中线性执行,后进先出。函数入栈即开始执行,出栈即彻底结束。事件循环本身从不干预栈内运行——它只在调用栈完全清空后才启动下一轮调度。这意味着:哪怕 setTimeout(0) 已就绪,只要栈里还有函数没执行完,它的回调就只能等待;微任务也无法打断正在运行的同步逻辑。
宏任务与微任务不是“快慢之分”,而是调度优先级的硬规则
宏任务(如 script 全局代码、setTimeout、setInterval、I/O、UI 渲染)每次仅执行一个;微任务(如 Promise.then/catch/finally、queueMicrotask、MutationObserver)则在**每个宏任务结束后、下一个宏任务开始前**被全部清空。
- Promise 构造函数体是同步执行的,只有 then/catch 回调才是微任务
- async/await 是语法糖,await 后的代码会被编译为 Promise 链的微任务
- 连续触发大量 Promise.then 可能导致微任务堆积,阻塞渲染,造成界面卡顿
渲染不是可选环节,而是事件循环的正式阶段
在浏览器中,事件循环每轮迭代包含明确的“渲染阶段”:微任务队列清空后,若页面状态有变更(如 DOM 修改、CSS 更新),浏览器会主动触发一次样式计算、布局、绘制流程。这个阶段不可跳过,也不能被 JS 代码直接控制。requestAnimationFrame 的回调就插入在此阶段之前,用于精准对齐下一帧。
立即学习“Java免费学习笔记(深入)”;
- 频繁修改 DOM + 紧跟读取 offsetHeight 等布局属性,会强制触发重排,拖慢循环节奏
- 把耗时操作拆成微任务(如 queueMicrotask)并不能加速渲染,反而可能延迟它
宿主差异决定行为边界
事件循环是宿主提供的机制,不同环境实现不同:
- 浏览器按清晰阶段划分:宏任务 → 微任务 → 渲染 → 下一宏任务
- Node.js 分为 timers、pending callbacks、poll、check、close callbacks 五个阶段,setImmediate() 和 setTimeout(fn, 0) 执行时机可能反转
- Web Worker 拥有独立的事件循环,与主线程内存隔离,但内部调度逻辑一致
不复杂但容易忽略


















