关键在于任务调度的精准分层:微任务用于轻量、确定性操作,宏任务保障渲染节奏,rAF对齐帧率,requestIdleCallback处理低优任务,避免单类任务霸占主线程。
关键不是“多用”哪一类任务,而是让每类任务出现在它该在的位置——微任务做轻量收尾,宏任务保渲染节奏。
把关键渲染操作锚定在宏任务边界
浏览器只有在宏任务执行完、微任务队列清空后,才会触发渲染机会。如果大量 DOM 更新、class 切换、transition 触发被塞进 Promise.then 链,就会延迟首屏内容上屏。
- 用 requestAnimationFrame 做动画或布局敏感更新:它属于宏任务,且与屏幕刷新率对齐,比 setTimeout(0) 更准、更顺
- 初始化批量 DOM 修改后,别立刻改样式,改用 queueMicrotask(() => element.classList.add('ready')) 统一触发,减少重排次数
- 避免在页面加载初期密集调用 Promise.all([...].map(() => Promise.resolve())),这会一次性压入几十个微任务,拖慢 FCP
按优先级分流任务,不堆在一个队列里
不同重要性的逻辑,应走不同事件循环通道,而不是靠 setTimeout(0) 和 Promise.then 比快慢。
- 立即响应类(如错误恢复、强制重绘):用 queueMicrotask 或 Promise.resolve().then,确保当前宏任务结束后马上执行
- 用户交互类(如输入反馈、按钮高亮):包裹在 requestAnimationFrame 中,绑定到帧节奏,防掉帧
- 普通后续处理(如接口返回后的状态更新):用 setTimeout(fn, 0),进入宏任务队列头部,不抢占渲染
- 低优后台任务(如埋点、预加载):用 requestIdleCallback,在浏览器空闲时执行,完全不抢资源
防止单类任务霸占主线程
微任务队列必须清空才能进渲染,宏任务连续执行太久会卡住交互。调度要有节制。
- 高优任务(如 rAF 中的动画逻辑)单次最多执行 2~3 个,之后主动让出,等下次帧再继续
- 监控任务耗时,若某 rAF 回调超过 5ms,后续同类任务自动降级到 setTimeout 队列
- 使用 requestIdleCallback 时,在 deadline.timeRemaining() < 1 时中断,保存中间状态,下次空闲再续
- 避免递归式微任务,比如在 queueMicrotask 回调里又调一次 queueMicrotask,容易造成微任务饥饿
微任务也有不可替代的价值
它不是性能敌人,而是可控的“紧贴同步之后”的时机,适合做确定性、轻量、需 DOM 已就位但尚未渲染的操作。
- DOM 插入后立刻读取 offsetHeight 或 getBoundingClientRect():用 Promise.resolve().then 可确保节点已挂载、布局已计算,但还没触发绘制
- MutationObserver 是微任务,适合监听关键节点变化并快速响应,但只观察必要属性(如 class、style),避免监听 childList 导致频繁触发
- 响应式框架(Vue/React)初始渲染默认走微任务,若首屏组件过多,可配合 Suspense + lazy 延迟非首屏部分的挂载


















