mutationobserver 是微任务,批量处理 dom 变动,在宏任务结束后、渲染前执行;回调中修改 dom 不触发新回调;observe 配置决定监听范围;disconnect 不清空队列,需 takerecords 提取积压记录。

MutationObserver 的观察逻辑不是即时响应 DOM 变动,而是等当前脚本执行完毕、所有同步操作都结束之后,再统一处理。它不走事件队列(Event Loop 的宏任务队列),而是被安排进微任务队列(Microtask Queue),在本轮宏任务结束后、渲染前立即批量执行。
变动收集是批量的,不是逐次触发
当连续修改 DOM(比如循环插入 50 个 div),MutationObserver 不会为每次插入调用一次回调,而是把这 50 次变动合并成一个 MutationRecord 数组,在一次回调中传入。这种设计避免了高频触发导致的性能压力。
- 哪怕只改一个属性,也会生成一条 MutationRecord,type 是 attributes
- 如果同时增删子节点又改了 class,回调里会收到多条 record,分别对应 childList 和 attributes
- 所有 record 按 DOM 变动发生的顺序排列,但不会跨宏任务——不同 setTimeout 里的改动会分两次回调
回调属于微任务,优先级高于 setTimeout
它的回调和 Promise.then()、queueMicrotask() 处于同一层级,在宏任务(如点击事件、定时器)结束后立刻清空,且必须等全部微任务执行完才进入渲染阶段。
- 这意味着你在回调里修改 DOM,这些新变动不会触发新一轮 MutationObserver 回调(除非已重新 observe)
- 但若在回调里触发 Promise.then(),那个 then 会在 MutationObserver 回调之后、同一批微任务中执行
- 而 setTimeout(() => {}, 0) 一定排在后面,属于下一轮宏任务
observe 启动后,只响应配置项覆盖的变动类型
是否触发回调,完全取决于你传给 observe() 的 config 对象。比如只设 childList: true,那属性或文本变化就完全不会被捕获。
- subtree: true 才能监听后代节点;否则只监控目标节点自身
- attributeFilter: ['class'] 可以缩小监听范围,比全量 attributes 更轻量
- attributeOldValue: true 必须配合 attributes: true 才生效,否则 mutation.oldValue 是 undefined
disconnect 不清空队列,takeRecords 可手动提取
调用 observer.disconnect() 会停止监听,但已发生却尚未处理的变动仍留在内部队列中。如需获取这些“积压”记录,要用 observer.takeRecords(),它返回 MutationRecord 数组并清空队列。
- 这个方法是同步的,不等待微任务,适合在断开前做兜底处理
- 如果没调用 takeRecords,那些未处理的变动就永久丢失了
- 重新调用 observe() 会开启新监听周期,之前积压的不会补发










