javascript事件循环由宿主环境主动调度,核心是调用栈、微任务队列和宏任务队列协作:栈空→执行一个宏任务→清空全部微任务→(浏览器)渲染→下一宏任务。

JavaScript 事件循环不是靠“定时轮询”驱动的,而是由宿主环境(浏览器或 Node.js)持续监控调用栈与队列状态后主动调度的结果。它的核心在于三个部件的协作:调用栈负责同步执行,微任务队列保障高优先级异步逻辑的连贯性,宏任务队列则提供主节奏控制;整个流程是“栈空 → 执行一个宏任务 → 清空全部微任务 → (浏览器)渲染 → 下一个宏任务”的严格接力,而非交替穿插。
调用栈清空是调度启动的前提
所有同步代码都在调用栈中按后进先出顺序执行。只要栈不为空,事件循环就一直等待——它不会强行中断当前函数。比如死循环 while(true) 或深度递归,会彻底堵住调度入口,页面直接卡死。这不是事件循环“失效”,而是它根本没机会开始工作。只有当栈完全清空,系统才进入下一步判断。
微任务队列必须一次性清空
Promise.then、queueMicrotask、MutationObserver 的回调都进入微任务队列。它们的执行时机非常明确:紧接在**当前宏任务结束之后、下一个宏任务开始之前**,且必须全部执行完毕,中间不插入任何新宏任务。这意味着:
- 连续十个
Promise.resolve().then()会一口气跑完,不会被 setTimeout 打断 - 哪怕在 setTimeout 回调里创建 Promise,其 then 也排在该 setTimeout 执行完、下一轮宏任务开始前
- 过度嵌套 microtask(如在 then 里反复 new Promise)可能挤占渲染时间,造成视觉卡顿
宏任务决定事件循环的基本节奏
setTimeout、setInterval、用户点击、fetch 响应、script 整体代码等都属于宏任务。它们按注册顺序排队,但每次循环只取一个执行。关键点在于:
- 一次宏任务执行期间产生的新宏任务(如又调了一次 setTimeout),要等到下一轮循环才可能被执行
- 浏览器每约 16ms 会强制插入一个渲染任务(如果 DOM 有变化),这也算一个宏任务
- Node.js 中的 setImmediate 和 requestIdleCallback 属于低优先级宏任务,排在普通宏任务之后
渲染是浏览器独有的显式环节
在浏览器中,UI 更新不是可选步骤,而是事件循环中固定的一环:每个宏任务执行完、微任务清空后,引擎会检查 DOM 是否变动,决定是否触发重排或重绘。这个过程不可跳过、不可手动触发,也不允许被 microtask 中断。正是这一机制,支撑了稳定的 60FPS 动画体验。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











