mutationobserver 通过微任务机制异步批量处理 dom 变动,避免同步回调导致的性能问题;变动被缓存为 mutationrecord 列表,在宏任务结束后统一执行回调,优先级与 promise.then 相同,确保布局完成但不跨帧。

MutationObserver 的微任务机制不是靠轮询或监听事件,而是由浏览器内核在每次 DOM 修改后自动触发调度——它把所有变动暂存,等当前 JS 执行栈清空、宏任务结束时,统一推入微任务队列执行回调。
变动收集是异步批量的
当你调用 observer.observe(target, config) 后,浏览器不会立刻响应每一次 DOM 操作。比如连续插入 5 个子节点,或反复修改同一个元素的 class,这些变化都会被缓存成 MutationRecord 列表,不立即执行回调。
- 只有当前宏任务(如 click 处理函数、setTimeout 回调)彻底执行完,JS 引擎空闲时,才会把这批记录一次性交给回调函数
- 这种“攒一批再处理”的方式,避免了因高频 DOM 变动导致的重复重排重绘和回调风暴
- 你拿到的 mutationsList 是已去重、按时间顺序排列的完整快照,不是逐次触发
回调执行属于微任务层级
它的回调和 Promise.then、queueMicrotask 属于同一优先级,在宏任务之间插入执行,比 requestAnimationFrame 更早,但比同步代码晚。
- 这意味着:你在回调里读取 offsetHeight 或 getBoundingClientRect(),得到的是真实渲染后的值(样式已计算、布局已完成)
- 但它仍处于当前帧内,不会等到下一帧绘制;若需确保视觉更新完成,可包裹 requestAnimationFrame
- 不手动调用 takeRecords() 时,未处理的记录会保留在 observer 内部,直到下次微任务触发
和 Mutation Events 的根本区别就在这里
Mutation Events(如 DOMNodeInserted)是同步触发的——DOM 一改,立刻进回调,JS 立刻执行,中间穿插样式计算和布局,主线程容易卡死。
- 而 MutationObserver 借助微任务,天然具备防抖+批处理能力,性能更可控
- 它不冒泡、不传播、不打断当前执行流,逻辑边界清晰,不会引发无限循环
- 这也是它能被 Vue/React 等框架底层用于同步 DOM 状态与响应式数据的关键原因











