【前端基础复盘】MutationObserver 接口的基本使用
MutationObserver
基本概念
监听指定DOM节点,当节点(指定的配置选项)修改时异步执行回调。
观察对象:整个文档、DOM树的一部分、某个元素、元素属性、子节点、文本或前三者任意组合的变化。
目的: 1. 取代废弃的 MutationEvent
基本用法
MutationObserver实例调用 MutationObserver构造函数并传入一个回调函数来创建一个观察者
let observer = new MutationObserver(()=>console.log('Dom was changed'))
observer()方法
MutationObserver 实例不会关联DOM的任何部分
所以要想将 observer 和 DOM 关联需要使用 MutationObserver 提供的 observe() 方法
observe() 方法有两个必传的参数:
-
需要观察变化的 DOM 节点
-
MutationObserverInit 对象: 用于控制观察那些方面变化的配置选项的字典 举个例子 创建一个观察者并配置它观察 body 元素上的属性变化
let observer = new MutationObserver(() => console.log('observe body changed'))
observer.observe(document.body, { attributes: true })
console.log('DOM属性改变前', document.body.style.background)
setTimeout(() => {
document.body.style.background = '#eeeeee'
console.log('DOM属性改变后', document.body.style.background)
}, 1000)
setTimeout(() => {
document.body.className = 'container'
console.log('body 元素的 className 改变了')
}, 1000)
setTimeout(() => {
document.body.setAttribute('data-id', 123456)
console.log('body 元素的 dataset.id 改变了')
}, 1000)
控制台 输出
回调和MutationRecord
每个回调函数都会收到一个 MutationRecord 实例的数组。
let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) );
mutationRecords 实例中包含了监听的节点的变化信息,比如该节点发生了什么变化、节点的哪一部分收到了影响。
let observer = new MutationObserver((MutationRecord) => {
console.log('<body> attribute changed')
console.log('MutationRecord', MutationRecord)
})
observer.observe(document.body, { attributes: true })
setTimeout(() => {
document.body.className = 'container'
document.body.setAttribute('data-id', 123)
document.body.style.background = '#eee'
console.log('body 元素的 attributes 改变了')
}, 1000)
同时监听多个属性变化,MutationRecord 信息按顺序存入 MutationRecord 实例数组。
MutationRecord
其中属性说明如下
属性 | 描述 | |
---|---|---|
target | 被修改影响的目标节点 | |
type | 字符串,表示变化的类型:“attribute”、"characterData"或"childList" | |
oldValue | 如果在 MutationObserverInit 对象(observe方法的第二个参数)中启用(attributeOldValue 或characterData OldValue 为 true),"attribute"或“characterData”的变化实践会设置这个属性为被替代的值,"childList"类型的变化始终将这个属性设置为 null。详见 下面oldValue 示例 | |
attributeName | 当变化的是Attributes 类型时,这里为被修改属性的名字,其它类型为 null | |
attributeNameSpace | 对于使用了命名空间的“attributes”类型的变化,这里保存被修改属性的名字,其它变化事件为null | |
addedNodes | 对于 “childList” 类型的变化,返回包含变化中添加节点的 NodeList 默认为空NodeList | |
removeNodes | 对于 “childList” 类型的变化,返回包含变化中删除节点的 NodeList 默认为空NodeList | |
previousSibling | 对于 “childList” 类型的变化,返回包含变化节点的前一个同胞Node默认为null | |
nextSibling | 对于 “childList” 类型的变化,返回包含变化节点的后一个同胞Node默认为null |
关于oldValue的示例
let observer = new MutationObserver((MutationRecord) => {
console.log('<body> attribute changed')
console.log('MutationRecord', MutationRecord)
})
observer.observe(document.body, {
attributes: true,
attributeOldValue: true,
characterDataOldValue: true,
})
setTimeout(() => {
document.body.className = 'container'
document.body.className = 'container2'
document.body.setAttribute('data-id', 123)
document.body.setAttribute('data-id', 456)
document.body.style.background = '#eee'
console.log('body 元素的 attributes 改变了')
}, 1000)
回调函数的第二个参数
回调函数的第二个参数是 观察变化的 MutationObserver 实例
disconnect()方法
默认情况下,被观察的对象只要不被垃圾回收,MutationObserver 的回调就会响应DOM变化事件,从而被执行。要提前终止执行回调,可以使用 disconnect() 方法。
let observer = new MutationObserver((MutationRecord) => {
console.log('<body> attribute changed')
console.log('MutationRecord', MutationRecord)
})
observer.observe(document.body, {
attributes: true,
attributeOldValue: true,
characterDataOldValue: true,
})
document.body.className = 'container'
document.body.className = 'container2'
document.body.setAttribute('data-id', 123)
document.body.setAttribute('data-id', 456)
document.body.style.background = '#eee'
observer.disconnect()
document.body.setAttribute('data-id', 789) // 没有日志输出了,不会再触发回调
要想让已经加入任务队列的回调执行,可以使用 setTimeout()让已经入列的回调执行完毕再调用 disconnect()。
let observer = new MutationObserver((MutationRecord) => {
console.log('<body> attribute changed')
console.log('MutationRecord', MutationRecord)
})
observer.observe(document.body, {
attributes: true,
attributeOldValue: true,
characterDataOldValue: true,
})
document.body.className = 'container'
document.body.className = 'container2'
document.body.setAttribute('data-id', 123)
document.body.setAttribute('data-id', 456)
document.body.style.background = '#eee'
// 输出上面已经入列的回调
setTimeout(() => {
observer.disconnect()
document.body.setAttribute('data-id', 789) // 没有日志输出了,不会再触发回调
}, 0)
复用 observe
多次调用 observe()方法,可以复用一个 MutationObserver 对象观察多个不同的目标节点。
MutationRecord 的 target 属性可以标识发生变化事件的目标节点。
let observer = new MutationObserver((MutationRecord) => {
console.log(MutationRecord.map((item) => item.target))
})
let childA = document.createElement('div')
let childB = document.createElement('span')
document.body.append(childA)
document.body.append(childB)
observer.observe(childA, { attributes: true })
observer.observe(childB, { attributes: true })
childA.setAttribute('id', '10086')
childB.setAttribute('id', '10000')
控制台输出结果
使用 disconnect() 方法 会中断所有节点的监听。
重用 MutationObserver
调用 disconnect()并不会销毁 MutationObserver 还可以重新使用这个观察者,再将
它关联到新的目标节点。
下面的示例在两个连续的异步块中先断开然后又恢复了观察者与元素
的关联
下面写一个两个连续的异步程序线断开然后再重新连接观察者
let observer = new MutationObserver((MutationRecord) => {
console.log(MutationRecord.map((item) => item.target))
})
let childA = document.createElement('div')
let childB = document.createElement('span')
document.body.append(childA)
document.body.append(childB)
observer.observe(childA, { attributes: true })
observer.observe(childB, { attributes: true })
childA.setAttribute('id', '10086')
childB.setAttribute('id', '10000')
setTimeout(() => {
observer.disconnect() // 执行完上面已在任务队列的回调函数后,断开连接
childB.setAttribute('id', '10000') // 不再监听和触发回调函数
}, 0)
// 重新建立连接
setTimeout(() => {
observer.observe(childA, { attributes: true })
observer.observe(childB, { attributes: true })
childA.setAttribute('id', '10086')
childB.setAttribute('id', '10000') // 触发回调函数
}, 0)
转载自:https://juejin.cn/post/7272938670338883603