事件循环是javascript运行时的调度中枢,通过宏任务队列与微任务队列的分层调度决定异步任务执行时机和顺序,调用栈清空后才触发,直接影响页面性能与响应。

事件循环不是“可选模块”,而是 JavaScript 运行时的调度中枢。它决定了异步任务何时执行、以什么顺序执行,直接影响页面是否卡顿、响应是否及时、资源是否被浪费。
宏任务与微任务的分层调度
事件循环靠两级队列协同工作:宏任务队列(Macrotask Queue)和微任务队列(Microtask Queue)。它们不是并列关系,而是有严格优先级——每次宏任务执行完后,会清空整个微任务队列,再取下一个宏任务。
- 常见宏任务:setTimeout、setInterval、I/O 回调、UI 渲染、script 标签加载
- 常见微任务:Promise.then/catch/finally、MutationObserver 回调、queueMicrotask()
- 错误示例:在 setTimeout(() => { heavyCalc(); }, 0) 中执行大量计算,会抢占下一帧渲染时机,导致掉帧
调用栈清空是触发点
事件循环不会主动“轮询”,它只在调用栈为空时才介入。这意味着同步代码必须先全部跑完,哪怕耗时几秒,事件循环也得等。
- 一个长循环(如 for (let i = 0; i
- 真正“非阻塞”的前提是:把耗时逻辑拆成小块,用 queueMicrotask 或 requestIdleCallback 分散执行
- 注意:await 并不等于“让出线程”,它只是暂停当前 async 函数执行,控制权交还给事件循环
async/await 的真实行为
async/await 是 Promise 的语法糖,本质仍是微任务驱动。每个 await 后的表达式一旦 resolve,其后续代码会被包装进微任务排队。
- 连续 await 不等于串行阻塞——只要 Promise 已 resolve,后续微任务会紧接上一个微任务执行
- 避免无意义链式 await:fetch(url1).then(() => fetch(url2)) 比 await fetch(url1); await fetch(url2) 更高效(后者强制两次微任务调度)
- 错误捕获要落在 await 表达式层级:try/catch 包裹单个 await,而非整个 async 函数体
浏览器渲染与事件循环的配合
浏览器把 requestAnimationFrame、样式计算、布局、绘制等作为宏任务的一部分,但有特殊时机约束:通常每帧开始前执行 rAF,帧末尾尝试渲染。
- 微任务总在渲染前执行,所以频繁操作 DOM + Promise.then 可能引发重排重绘抖动
- 需要视觉反馈的任务(如按钮点击态),优先用 rAF 而非 setTimeout(0),更贴近帧节奏
- 长时间动画逻辑建议用 requestIdleCallback 切片,避免挤占用户交互任务
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











