首先MutationObserver接口是替换废弃的MutationEvent接口(MutationEvent定义了一系列DOM变化时触发的事件,浏览器处理事件的实现机制不同导致出现了严重的的性能问题)
MutationObserver接口,可以在DOM被修改时,异步执行回调;可以观察整个文档、DOM树的一部分,或某个元素。除此,还可以观察元素属性(attributes)、子节点(childList)、文本(characterData),或者前三者任意组合的变化。
基本用法:
MutationObserver的实例需要通过调用MutationObserver构造函数并传入一个回调函数来创建
1、observe()方法:
新创建的MutationObserver实例不会关联DOM的任何部分。要把observer与DOM关联起来,需要使用observe()。这个接收两个必须的参数:observe(要观察变化的DOM节点,MutationObserverInit对象)
MutationObserverInit对象用于控制观察哪些方面的变化,是一个键/值形式配置选项的字典。
例:配置观察<body>元素上的属性变化:
let mutation1 = new MutationObserver(()=>{console.log('<body>属性变化')});
mutation1.observe(document.body,{attributes:true});
执行以上代码后,<body>元素上任何属性发生变化都会被这个MutationObserver实例发现,然 后就会异步执行注册的回调函数。<body>元素后代的修改或其他非属性修改都不会触发回到进 入任务队列
验证:
document.body.className='foo'
console.log('body class变化');
// body class变化
// <body>属性变化
注:回调中的console.log是后执行的。说明回调并非与实际的DOM变化同步执行
2、回调与MutationRecord
每个回调都会收到一个MutationRecord实例数组。MutationRecord包含发生了什么变化以及DOM的哪部分受到了影响。因为回调执行前可能同时发生多个满足观察条件的事件,所以每次执行回调都会传入一个包含按顺序入队的MutationRecord实例的数组
let mutation2 = new MutationObserver((MutationRecords) => { console.log(MutationRecords) });
mutation2.observe(document.body, { attributes: true });
document.body.setAttribute('foo', 'bar');
// [
// {
// addedNodes:NodeList[],
// attributeName:"foo",
// attributeNamespace:null,
// nextSibling:null
// oldValue:null
// previousSibling:null
// removedNodes:NodeList[]
// target:body
// type:"attributes"
// }
// ]
例:涉及命名空间的类似变化
document.body.setAttributeNS('baz','foo', 'bar');
// [
// {
// addedNodes:NodeList[],
// attributeName:"foo",
// attributeNamespace:'baz',
// nextSibling:null
// oldValue:null
// previousSibling:null
// removedNodes:NodeList[]
// target:body
// type:"attributes"
// }
// ]
连续修改会生成多个MutationRecord实例,下次回调执行就会收到包含所有这些实例的数组,顺序为变化事件发生的顺序
document.body.className='foo';
document.body.className='bar';
document.body.className='baz';
[MutationRecord, MutationRecord, MutationRecord]
MutationRecord实例属性:
target | 被修改影响的目标节点 |
type | 字符串,标识变化的类型:"attributes "、 " characterData" 或 "childList" |
oldValue | 如果在MutationObserverInit对象中启用 (attributeOldValue或characterData OldValue为true), "attributes"或"characterData"的变化事件会设置这个属性为被替代的值;"childList"类型的变化始终将这个属性设置为null |
attributeName | 对于"attributes"类型的变化,这里保存被修改属性的名字 |
attributeNamespace | 对于使用了命名空间的"attributes"类型的变化,这里保存被修改属性的名字其他变化事件会将这个属性设置为null |
addedNodes | 对于"childList"类型的变化,返回包含变化中添加节点的NodeList默认为空 NodeList |
removedNodes | 对于"childList"类型的变化,返回包含变化中删除节点的NodeList删除节点的NodeList默认为空 NodeList |
previousSibling | 对于"childList"类型的变化,返回变化节点的前一个同胞Node默认为null |
nextSibling | 对于"childList"类型的变化,返回变化节点的后一个同胞Node默认为null |
传给回调函数第二个参数是观察变化的MutationObserver实例
let mutation3 = new MutationObserver((MutationRecords,MutationObserver) => { console.log(MutationRecords,MutationObserver) });
mutation3.observe(document.body, { attributes: true });
document.body.className='foo';
3、disconnect()
默认情况下,只要被观察的元素不被垃圾回收,MutationObserver的回调就会响应DOM变化事件,从而执行。要提前终止执行回调,可以调用disconnect()
例:正常调用,不仅会停止此后变化事件的回调,也会停止已经加入任务队列的回调
let disconnect = new MutationObserver((MutationRecords) => { console.log(MutationRecords)});
disconnect.observe(document.body, { attributes: true });
document.body.className='foo';
disconnect.disconnect();
document.body.className='bar';
//没有日志输出
要想让已加入任务队列的回调执行,可以使用setTimeOut()让已经入列的回调执行完毕,再调用disconnect()
setTimeout(()=>{
disconnect.disconnect();
document.body.className='bar';
},0)
// [
// {
// addedNodes:NodeList[],
// attributeName:"foo",
// attributeNamespace:null,
// nextSibling:null
// oldValue:null
// previousSibling:null
// removedNodes:NodeList[]
// target:body.bar
// type:"attributes"
// }
// ]
4、复用MutationObserver
多次调用observe()方法,可以重复使用一个MutationObserver对象观察多个不同的目标节点。此时,mutationRecord的target属性可以标识目标节点
let mutation4 = new MutationObserver((MutationRecords) => console.log(MutationRecords.map(x=>x.target)));
向页面body添加两个子节点
let childA = document.createElement('div');
childB = document.createElement('span');
document.body.appendChild(childA);
document.body.appendChild(childB);
观察两个子节点
mutation4.observe(childA,{attributes:true})
mutation4.observe(childB,{attributes:true})
修改两个子节点属性
childA.setAttribute('foo','bar')
childB.setAttribute('foo','bar')
//[div,span]
5、重用MutationObserver
调用disconnect()方法并不会结束MutationObserver的生命。还可以重新使用这个观察者,再将它关联到新的目标节点。
例:先断开然后又恢复了观察者与<body>元素的关联
let mutation5 = new MutationObserver((MutationRecords) => MutationRecords.map((x)=>console.log(x.attributeName)));
mutation5.observe(document.body, { attributes: true });
这行代码会触发变化事件
document.body.setAttribute('foo','bar')
setTimeout(()=>{
mutation5.disconnect();
//这行代码不会触发变化事件
document.body.setAttribute('bar', 'baz');
},0)
setTimeout(()=>{
mutation5.observe(document.body, { attributes: true });
//这行代码会触发变化事件
document.body.setAttribute('baz', 'qux');
},0)
//foo,baz