
微任务队列不是“加速器”,而是调度的节拍器——它不提速单个操作,但能让你把零散的状态更新、DOM 修改、回调执行攒到同一帧渲染前统一处理,从而避开重复计算、无效重排和同步阻塞。
用 queueMicrotask 做“本轮末尾提交”
核心是延迟执行、聚合变更、避免中间态。比如连续调用 5 次 setState,若每次立即触发渲染,会浪费 4 次;而用微任务缓冲后,只在当前宏任务结束、浏览器绘制前执行一次最终合并结果。
- 维护一个待处理缓冲区(如数组或 Map),所有变更先写入,不直接应用
- 用 pending 标志控制是否已安排微任务:未安排时调用 queueMicrotask(() => flush())
- flush() 清空缓冲、合并逻辑(取最新值、去重、批量 DOM 更新)、重置 pending = false
- 注意:不要在 flush 中又触发新状态变更并再次调用 queueMicrotask,否则易形成递归微任务链,阻塞渲染
区分上下文,别跟框架“抢活”
React、Vue 等现代框架已在事件处理器(如 click、input)中默认启用批量更新。你在自定义逻辑里硬套微任务,反而可能破坏已有优化。
- 原生事件或框架生命周期钩子内,通常无需再封装 —— 框架已帮你 batch
- 在 setTimeout、Promise.then、fetch 回调 等非批量上下文中,才需要手动用 queueMicrotask 聚合
- 可封装 batchUpdate(fn) 工具函数:内部检测是否已在批量环境,是则直行;否则用微任务延迟
高频场景加时间维度:防抖 + 微任务
仅靠“本轮末尾”仍不够——用户快速输入、滚动、拖拽时,每帧都可能触发多次,导致每帧都 flush。需叠加毫秒级节流。
- 不每次变更都进微任务,而是设一个 debounceId(如 setTimeout)
- 每次新变更清除旧定时器,重设(例如 16ms 后触发)
- 定时器到期后,再用 queueMicrotask 执行最终 flush —— 既抑制抖动,又确保在渲染前完成
- 示例:用户连输 10 字符,只在停顿 16ms 后一次性提交,而非发 10 次
警惕微任务饥饿与隐式阻塞
微任务优先级高,但不等于可以无节制使用。滥用会挤占主线程,让页面“卡在看不见的地方”。
- 避免在 flush 中做耗时同步操作(如遍历 10 万条数据)——这仍是主线程阻塞
- 大计算应拆分:用 queueMicrotask 分片执行,或移交 Web Worker
- 慎用嵌套 Promise.then 链反复触发 DOM 更新——攒批或交由 requestIdleCallback 延后处理
- Chrome 对微任务深度有限制(约 1000 层),递归调用极易触达,引发性能断崖











