初面网初面网

MutationObserver

MutationObserver 是一个用于监听 DOM 变化的接口,它可以在 DOM 发生变化时触发回调函数。

它的出现取代了废弃的 MutationEvent

假设新建 MutationObserver 的实例 observer ,默认是不会关联到DOM的,需要用 observe() 方法来关联到具体的 DOM 节点。

observe() 方法接受两个参数,第一个参数是要观察的 DOM 节点,第二个参数是一个 MutationObserverInit 配置对象。

const observer = new MutationObserver(()=>console.log('DOM 发生变化'));
observer.observe(document.body, { attributes: true });

回调与MutationRecord

每个回调都会收到一个 MutationRecord 数组,包含了发生变化的 DOM 节点和变化的类型。

const observer = new MutationObserver((mutationRecords) => {
  console.log(mutationRecords);
});

连续修改会生成多个 MutationRecord 实例。下次回调执行时会收到包含所有这些实例的数组。

MutationRecord 实例的属性包括:

属性说明
target发生变化的 DOM 节点
type变化的类型,如 'attributes'、'childList' 等
oldValue变化前的值(对于 attributes 类型)
attributeName变化的属性名(对于 attributes 类型)
attributeNamespace变化的属性命名空间(对于 attributes 类型)
addedNodes对于 childList 类型,返回新增的节点列表
removedNodes对于 childList 类型,返回移除的节点列表
previousSibling对于 childList 类型,返回变化节点的前一个兄弟节点
nextSibling对于 childList 类型,返回变化节点的后一个兄弟节点

复用 observer

多次调用可以复用观察。

const observer = new MutationObserver(()=>console.log(
  mutationRecords.map((x) => x.target)
));
const itemA = document.createElement('div');
const itemB = document.createElement('span');

observer.observe(itemA, { attributes: true });
observer.observe(itemB, { attributes: true });

disconnect()方法

如果要终止观察,调用 disconnect() 方法即可。

const itemA = document.createElement('div');
observer.observe(itemA, { attributes: true });
itemA.setAttribute('id', 'oldItem');
observer.disconnect();
itemA.setAttribute('id', 'newItem');

disconnect() 方法不代表观察的销毁,仍可以再次用observe()方法再次建立观察。

MutationObserverInit 与观察范围

控制对目标节点的观察范围。观察者可以观察的事件包括:属性变化、文本变化和子节点变化。

属性说明
subtree布尔值,表示除了目标节点,是否观察目标节点的子树。false不观察,true观察
attributes布尔值,表示是否观察目标节点的属性变化。默认false
attributeFilter字符串数组,表示要观察哪些属性的变化
attributeOldValue布尔值,表示 MutationRecord 是否记录变化之前的属性值。设为true,attributes的值也会变为true。默认false
characterData布尔值,表示修改字符数据是否触发变化事件。默认false
characterDataOldValue布尔值,表示 MutationRecord 是否记录变化之前的文本值。设为true,characterData的值也会变为true。默认false
childList布尔值,表示是否观察目标节点的子节点变化。默认false

调用 observe() 方法时,attribute、characterData、childList中至少要有一项为true,否则抛出错误。

注意,子节点的重排可能会报告两次变化,因为在技术上会涉及先移除、再添加。

如果设置了 subtree 为 true,则会观察目标节点及其子树的变化。值得留意的是,子树的子节点被移出该子树后仍能被观察到,意即它仍会触发变化事件。

异步回调与记录队列

变化的信息保存在 MutationRecord 实例中,然后添加到记录队列,该队列对每个 MutationObserver 实例都是唯一的。

每次 MutationRecord 被添加到 MutationObserver 的记录队列时,仅当之前没有已排期的微任务回调时(队列中微任务长度为0),才会将观察者注册的回调(在初始化 MutationObserver 时传入 )作为微任务调度到任务队列上。这样可以保证记录队列的内容不会被回调处理两次。

在回调的微任务异步执行期间,有可能又发生更多变化事件,因此,被调用的回调会接收到一个 MutationRecord 实例的数组,顺序为它们进入记录队列的顺序。回调要负责处理这个数组的每一个实例,因为函数退出之后这些实现就不存在了。回调执行后,这些 MutationRecord 就用不着了,因此记录队列会被清空,其内容会被丢弃。

使用 takeRecords() 方法可以获取当前记录队列中的所有 MutationRecord 实例,并清空队列。

回收

MutationObserver 实例对目标节点是弱引用,但节点对实例却是强引用。因此,当节点被移除时,关联的 MutationObserver 也会被垃圾回收。

引用

记录队列中的每个 MutationRecord 实例至少包含对已有 DOM 节点的一个引用。如果变化是 childList 类型,则会包含多个节点的引用。

有时可能需要保存某个观察者的完整变化记录。保存这些 MutationRecord 实例,也就会保存它们引用的节点,因而会妨碍这些节点被回收。

为尽快释放内存,可从每个 MutationRecord 实例中抽取最有用的信息,存到新对象中,然后抛弃 MutationRecord。

更新于 2026/7/3