MutationObserver 的核心是异步批量监听,依赖微任务机制在 JS 执行栈清空后统一处理 DOM 变化;变更由浏览器内核同步记录入队,合并为 MutationRecord 数组,回调中 DOM 为最终态但未渲染。

MutationObserver 的核心在于“异步批量监听”,它不直接响应每一次 DOM 修改,而是把变化暂存、合并,等当前 JS 执行栈清空后,再统一处理——这背后依赖的是浏览器事件循环中的微任务机制。
它是怎么收集 DOM 变化的?
当代码执行 element.appendChild()、node.setAttribute() 或 textNode.textContent = 'xxx' 时,浏览器内核会立即记录这次变更,并归入一个内部的“变更记录队列”。这个过程是同步且轻量的,不触发回调,也不影响当前脚本运行。
- 每条记录是一个
MutationRecord对象,含type(如'childList')、addedNodes、oldValue等字段 - 只要变化符合你配置的选项(比如开了
childList: true),就会被收录 - 连续多次修改同一节点,通常会被合并成一条或多条记录,而非逐次触发
为什么回调总在 JS 执行完才跑?
因为 MutationObserver 的回调被安排为微任务(microtask),和 Promise.then()、queueMicrotask() 同级。它严格遵循事件循环规则:
- 当前宏任务(如点击事件、
setTimeout回调)执行完毕 - 立刻清空微任务队列:所有已排队的
MutationObserver回调 +Promise回调依次执行 - 微任务全部执行完,才进入渲染阶段(paint)
这意味着:你在回调里读到的 DOM,一定是本次 JS 所有操作完成后的最终状态;但还**没渲染上屏**——若需获取布局信息(如 offsetHeight),得配合 requestAnimationFrame。
它和旧版 Mutation Events 为啥不可同日而语?
废弃的 DOMNodeInserted 是同步事件,每次 DOM 修改都立即触发,容易造成:
- 回调嵌套调用,栈溢出风险
- 频繁重排重绘,页面卡顿
- 无法合并变化,性能雪崩
MutationObserver 则通过“延迟+聚合”彻底规避这些问题:一次微任务只执行一次回调,传入的是整批变化记录数组,逻辑更可控,开销更小。
实际开发中要注意什么?
几个关键细节常被忽略:
-
必须手动调用
observe()才开始监听,创建实例本身不生效 -
默认只监听目标节点自身,要监听子树得显式设
subtree: true -
旧值需要主动开启:想拿到修改前的 class 值,除了
attributes: true,还得加attributeOldValue: true -
记得清理:不再需要时调用
disconnect(),否则可能引发内存泄漏(尤其在单页应用组件卸载时)

















