一、MutationObserver 接口说明
此接口可以在 DOM 被修改时异步执行回调。使用 MutationObserver 可以观察整个文档、DOM 树的一部分,或某个元素。此外还可以观察元素属性、子节点、文本,或者前三者任意组合的变化。
DOM3 中新引进 MutationObserver 接口是为了取代废弃的 DOM2 中的 MutationEvent。
二、基本用法
MutationObserver 的实例要通过 MutationObserver 构造函数并传入一个回调函数来创建。
let observer = new MutationObserver(() => console.log('Dom was mutated~'))
1. observe() 方法
新创建的 MutationObserver 实例不会关联 DOM 的任何部分。需要使用 ovserve()方法与 DOM 关联起来。
此方法需传两个必须的参数:
- 要观察其变化的 DOM 节点
- 一个 MutationObserverInit 对象
MutationObserverInit 对象:用于控制哪些方面的变化,是一个键/值对形式配置选项的字典。
// 创建一个观察者(observer)并配置它观察 body 元素上的属性变化
let observer = new MutationObserver(() => {
console.log('body attributes changed~')
})
observer.observe(document.body, { attributes: true })
document.body.className = 'foo'
console.log('Changed body class')
// Changed body class
// body attributes changed~
执行上述代码后,body 元素上的任何属性发生变化都会被这个 MutationObserver 实例发现,然后会异步执行注册的回调函数。而 body 元素后代的修改或其他非属性变化修改都不会触发回调进入任务队列。
注意,回调中的 console.log()是后执行的,这表明回调并非与实际的 DOM 变化同步执行。
2. 回调与 MutationRecord 参数
每个回调都会受到一个 MutationRecord 实例的数组。MutationRecord 实例包含信息包括发生了什么变化、以及 DOM 的哪一部分受到了影响。回调的第二个参数是 MutationObserver 的实例。
// 连续修改会生成多个MutationRecord实例。回调执行时会受到包含所有这些实例的数组,顺序为变化事件顺序。
let observer = new MutationObserver((MutationRecords, mutationObserver) => {
console.log(MutationRecords, mutationObserver)
})
observer.observe(document.body, { attributes: true })
document.body.setAttribute('foo', 'bar')
document.body.className = 'testName'
// [MutationRecord, MutationRecord], MutationObserver
执行效果如下图:

MutationRecord 实例属性说明
属性 |
说明 |
target |
被修改影响的目标节点 |
type |
字符串,表示变化的类型:"attributes"、"characterData"、"childList" |
oldVal |
如果在 MutationObserverInit 对象中启用(attributeOldValue 或 characterData OldValue 为 true),"attributes" 或 "characterData" 的变化事件会设置这个属性为被替代的值"childList" 类型的变化始终将这个属性设置为 null |
attributeName |
对于 "attributes" 类型的变化,这里保存被修改属性的名字。其他变化事件会将此设置为 null |
attributeNamespace |
对于使用了命名空间的 "attributes" 类型的变化,这里保存被修改属性的名字。其他变化事件会将此设置为 null |
addedNodes |
对于 "childList" 类型的变化,返回包含变化中添加节点的 NodeList。默认为空 NodeList |
removedNodes |
对于 "childList" 类型的变化,返回包含变化中删除节点的 NodeList。默认为空 NodeList |
previousSibling |
对于 "childList" 类型的变化,返回变化节点的前一个同胞 Node。默认为 null |
nextSibling |
对于 "childList" 类型的变化,返回变化节点的后一个同胞 Node。默认为 null |
3. disconnet() 方法
默认情况下,只要被观察的元素不被垃圾回收,MutationObserver 的回调就会响应 DOM 变化事件而执行。要提前终止回调,可以调用 disconnet() 方法。
let observer = new MutationObserver(() => {
console.log('body attributes changed~')
})
observer.observe(document.body, { attributes: true })
observer.disconnet()
document.body.className = 'foo'
// (没有日志输出)
重用 MutationObserver:调用 disconnet() 方法并不会结束 MutationObserver 的生命。还可以重新使用这个观察者,再讲它关联到新的目标节点。
三、MutationObserverInit 观察范围
MutationObserverInit 对象用于控制对目标节点的观察范围。例:属性变化、文本变化和节点变化。
属性 |
说明 |
subtree |
boolean,表示除了目标节点,是否观察其子树(后代)。默认为 false,只观察目标节点的变化 |
attributes |
boolean,表示是否观察目标节点的属性变化。默认为 false |
attributeFilter |
字符串数组,表示要观察哪些属性的变化。把这个值设置为 true,也会将 attributes 值转换为 true。默认为观察所有属性 |
attributeOldValue |
boolean,表示 MutationRecord 是否记录变化之前的值。把这个值设置为 true,也会将 attributes 值转换为 true。默认为 false |
characterData |
boolean,表示修改字符数据是否触发变化事件 |
characterOldValue |
boolean,表示 MutationRecord 是否记录变化之前的值。把这个值设置为 true,也会将 characterData 值转换为 true。默认为 false |
childList |
boolean,表示修改目标节点的子节点是否触发变化事件。默认为 false |
调用 observe() 时,MutationObserverInit 对象中 attributes、characterData、childList 属性必须至少有一项为 true。
<!-- 观察子节点 -->
<body>
<div id="con"></div>
<script>
const conEle = document.getElementById('con')
let observer = new MutationObserver((MutationRecords, mutationObserver) => {
console.log(MutationRecords)
})
observer.observe(conEle, { childList: true })
conEle.appendChild(document.createElement('p'))
</script>
</body>
// [MutationRecord]
打印效果如下图:

四、异步回调与记录队列
1. 记录队列
每次 MutationRecord 被添加到 MutationObserver 的记录队列时,仅当之前没有已排期的微任务回调时(队列中微任务长度为 0),才会将观察者注册的回调(在初始化 MutationObserver 时传入)作为微任务调度到任务队列上。这样可以保证记录队列的内容不会被回调处理两次。
不过在回调的微任务异步执行期间,有可能又会发生更多的变化事件。因此被处理的回调会接收到一个 MutationRecord 实例的数组,顺序为它们进入记录队列的顺序。回调要负责处理这个数组的每一个实例,因为函数退出之后这些实例就不存在了。回调执行后,这些 MutationRecord 就用不着了,因此记录队列会被清空,其内容会被丢弃。
2. takeRecords() 方法
调用 MutationObserver 实例的 takeRecords() 方法可以清空记录队列,取出并返回其中的所有 MutationRecord 实例。
这在希望断开与观察目标的联系,但有希望处理由于调用 disconnet() 而被抛弃的记录队列中的 MutationRecord 实例时比较有用。
let observer = new MutationObserver((MutationRecords) => {
console.log(MutationRecords)
})
observer.observe(document.body, { attributes: true })
document.body.className = 'foo'
document.body.className = 'bar'
console.log(111, observer.takeRecords())
console.log(222, observer.takeRecords())
// 111 [MutationRecord, MutationRecord]
// 222 []