微任务队列总在渲染前被完全清空;同步代码结束后立即执行所有微任务(如promise.then、mutationobserver),不中断渲染;渲染仅在有更新机会时发生,且必在微任务之后;强制可见需分属不同宏任务。

微任务队列总是在渲染之前执行,且必须被完全清空,之后浏览器才可能触发一次渲染更新。
微任务执行时机:同步代码一结束就立即运行
只要当前宏任务中的同步代码执行完毕(即调用栈变空),引擎会立刻检查微任务队列。不管队列里有多少个微任务——哪怕是在执行过程中新加入的,也会全部连续执行完,中间不穿插任何渲染或宏任务。
- 常见微任务来源包括:Promise.then/catch/finally、MutationObserver、queueMicrotask()
- 例如:
Promise.resolve().then(() => console.log('A'))的回调不会等到下一帧,而是在当前脚本末尾、渲染前输出 - 如果在某个
.then中又调用了queueMicrotask或另一个Promise.then,新任务仍属于本轮微任务,会被顺次执行
渲染不是每次 Event Loop 都发生
浏览器只在“有渲染机会”(rendering opportunity)时才会进行 UI 更新,比如屏幕即将刷新、页面处于前台、样式或布局已变更等。但无论是否渲染,微任务都一定先于这个机会被执行。
- 也就是说:微任务 → (可能)渲染 → 下一个宏任务
- 即使你连续修改 DOM 十次,只要都在同一个宏任务内完成,用户也只会看到最终结果,因为所有变更都在一次渲染中合并呈现
- 想强制让某次 DOM 变更立刻可见?得让它落在两个独立的宏任务中,比如用
setTimeout(fn, 0)或requestAnimationFrame
为什么不能跳过微任务直接渲染?
这是为了保证 JavaScript 执行逻辑的一致性与可预测性。如果渲染插在微任务中间,就可能出现“DOM 已更新但 Promise 回调还没跑”的状态撕裂问题。
- 比如监听 DOM 变化的 MutationObserver 就是微任务,它必须在渲染前拿到完整的变更记录,否则观察结果就不完整
- 同理,await 后续代码也依赖微任务机制来衔接,跳过会导致异步流程错乱
- 这种设计让开发者能确信:“只要我在 Promise.then 里读 DOM,看到的就是上一步同步操作后的最新结构”










