微任务必须在每次渲染前被完全执行完毕,其执行时序固定为:宏任务→全部微任务→渲染→下一个宏任务;它不比宏任务更快,而是严格卡在渲染前的确定窗口,保障dom状态与视觉呈现的一致性。

因为微任务队列的清空是浏览器渲染流程中一个不可跳过的强制环节——它必须在每次渲染前被完全执行完毕,而不是“优先级更高”或“跑得更快”。这种紧密绑定不是设计上的偏好,而是渲染管线的硬性阶段要求。
微任务与渲染之间存在明确的执行时序约束
浏览器规范规定:只要一次宏任务(比如点击事件、一段 script 执行)结束、调用栈变空,引擎必须立刻执行所有已排队的微任务,且不中断、不穿插、不等待任何信号;只有等微任务队列彻底清空后,才允许进入渲染阶段。这个顺序是固定的:宏任务 → 全部微任务 → 渲染 → 下一个宏任务。
- 这意味着哪怕你在 Promise.then 里又调用 queueMicrotask(),新任务也会被纳入本轮执行,不会推迟到下次渲染前
- 而 setTimeout 回调属于宏任务,必然落在下一轮事件循环,中间可能插入用户交互、其他事件甚至一次完整渲染
- 所以“贴近渲染”,本质是它被安排在渲染发生的前一刻,是渲染的前置条件,而非竞争关系
这种安排服务于 DOM 状态的一致性保障
很多关键机制依赖微任务在渲染前完成,否则视觉与逻辑就会脱节:
- MutationObserver 需要拿到完整的 DOM 变更快照,如果渲染先发生,变更记录就可能被截断
- Promise.then 或 await 后续代码,常用来读取刚写入的 DOM(如 innerHTML 更新后的 offsetHeight),这些值只有在渲染前读才是稳定可靠的
- 框架如 Vue/React 的响应式更新,也利用微任务确保状态同步与视图更新之间的因果链不被渲染打断
它不是“更快”,而是“不绕开渲染时机”
微任务并不比宏任务执行得更快,只是它不参与宏任务之间的调度竞争。它的执行窗口是确定的:紧贴在每个宏任务末尾、渲染开始之前。这个位置让它天然成为“DOM 修改之后、用户看到之前”的逻辑承载区。
- 你改了 class、插入了元素、触发了 reflow,这些变更已写入 DOM 树,但尚未绘制到屏幕上
- 此时 Promise.then 就能准确读取布局信息,而 setTimeout 则大概率要等到下一帧,期间 DOM 可能已被其他操作修改
- 所以“贴近渲染进程”,指的是它卡在渲染的门槛上,是唯一能稳定衔接 DOM 操作与视觉呈现的异步通道
不复杂但容易忽略:微任务不是为了抢时间,而是为了守规矩——守的是浏览器渲染流程的规矩。











