# MutationObserver(ie11以下不支持)
可以在DOM修改时异步执行回调,前身是MutationEvent(可以支持到IE9,是个以后被弃用的API)
# MutationEvent
MutationEvent总共有7种事件:DOMNodeInserted、DOMNodeRemoved、DOMSubtreeModified、DOMAttrModified、 DOMCharacterDataModified、DOMNodeInsertedIntoDocument和DOMNodeRemovedFromDocument。
MutationEvent的兼容性:
- MutationEvent在IE浏览器中最低支持到IE9
- 在webkit内核的浏览器中,不支持DOMAttrModified事件
- IE,Edge以及Firefox浏览器下不支持DOMNodeInsertedIntoDocument和DOMNodeRemovedFromDocument事件
MutationEvent中最令人诟病的就是性能以及安全性的问题
document.addEventListener('DOMNodeInserted', function() {
var newEl = document.createElement('div');
document.body.appendChild(newEl);
});
document下的所有DOM添加操作都会触发DOMNodeInserted方法,这时就会出现循环调用DOMNodeInserted方法,导致浏览器崩溃。还有就是MutationEvent是事件机制,因此会有一般事件都存在的捕获和冒泡阶段,此时如果在捕获和冒泡阶段又对DOM进行了操作会拖慢浏览器的运行。
另一点就是MutationEvent事件机制是同步的,也就是说每次DOM修改就会触发,修改几次就触发几次,严重降低浏览器的运行,严重时甚至导致线程崩溃
# MutationObserver
MutationObserver的实例要通过MutationObserver构造函数并传入一个回调函数来创建
let observer = new MutationObserver(()=>console.log(1))
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<div id='target' class='block' name='target'>
target的第一个子节点
<p>
<span>target的后代</span>
</p>
</div>
</body>
</html>
<script type="text/javascript">
var target=document.getElementById('target');
var i=0
var observe=new MutationObserver(function (mutations,observe) {
i++;
console.log(i) //1 MutationObserver的callback回调函数是异步的,可以将段时间内的多次操作结果一次性调用回调函数,提高性能。
});
observe.observe(target,{ childList: true});
target.appendChild(document.createTextNode('1'));
target.appendChild(document.createTextNode('2'));
target.appendChild(document.createTextNode('3'));
</script>
# observe()
两个必须的参数:1.要观察的dom节点 2.一个MutationObserverInit对象
MutationObserverInit对象: The options object must set at least one of 'attributes', 'characterData', or 'childList' to true.
let observer = new MutationObserver(()=>console.log(1))
observer.observe(document.body,{attributes:true})
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<div id="k"></div>
</body>
</html>
<script type="text/javascript">
let observer = new MutationObserver(() => console.log('<body> attributes changed'));
observer.observe(document.body, { attributes: true });//页面初始化dom触发一次
document.body.className = 'foo'; //更改触发一次
console.log('Changed body class');
setTimeout(()=>{
document.getElementById("k").innerHTML=333//更改子元素或者非body的属性内容泽不会触发函数执行
// document.body.className = 'foo1'; //如果不注释,会执行两次<body> attributes changed
})
//Changed body class
//<body> attributes changed
//回调的console是后执行的,表明回调并非与实际的DOM变化同步执行
</script>
# MutationRecord
每个回调都会收到一个MutationRecord实例的数组,第二个参数还可以传回MutationObserver实例自身
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<div id="k"></div>
</body>
</html>
<script type="text/javascript">
let observer = new MutationObserver((mutationrecords,k) => {
console.log(mutationrecords)
console.log(k)
});
observer.observe(document.body, { attributes: true });
document.body.className = 'foo';
document.body.id = 'foo';
console.log('Changed body class');
setTimeout(()=>{
document.getElementById("k").innerHTML=333
document.body.className = 'foo1';
})
//Changed body class
//[MT,MT] 有两个MutationRecord的数组
//MutationObserver {}
//[MT] 有一个MutationRecord的数组
//MutationObserver {}
</script>
# mutationobserver之disconnect
只要被观察的元素不被垃圾回收,mutationobserver的回调就会响应dom变化事件。可使用disconnect中止回调(!同时还未执行的回调也会终止,解决办法:setTimeout)
observer.disconnect()
# 复用MutationObserver
可以定义一个实例,监听多个dom对象,注意:disconnect调用后会停止所有的监听!
observer.observe(childA, { attributes: true });
observer.observe(childB, { attributes: true });
# 重用MutationObserver
disconnect()并不会结束MutationObserver生命,还可以重新使用
let observer = new MutationObserver((mutationrecords,k) => {
console.log(mutationrecords)
console.log(k)
});
observer.observe(document.body, { attributes: true });
document.body.className = 'foo';
console.log('Changed body class');
observer.disconnect()
setTimeout(()=>{
observer.observe(document.body, { attributes: true });
document.body.id = 'foo';
})
# MutationObserverInit
| 属性 | 作用 |
|---|---|
| attributes | 设置true,表示观察目标属性的改变 |
| attributeFilter | 如果不是所有的属性改变都需要被观察,并且attributes设置为true或者被忽略,那么设置一个需要观察的属性本地名称(不需要命名空间)的列表 |
| attributeOldValue | 如果属性为true或者省略,则相当于设置为true,表示需要记录改变前的目标属性值,设置了attributeOldValue可以省略attributes设置 |
| characterData | 设置true,表示观察目标数据的改变 |
| characterDataOldValue | 如果characterData为true或省略,则相当于设置为true,表示需要记录改变之前的目标数据,设置了characterDataOldValue可以省略characterData设置 |
| childList | 设置true,表示观察目标子节点的变化,比如添加或者删除目标子节点,不包括修改子节点以及子节点后代的变化 |
| subtree | 设置为true,目标以及目标的后代改变都会观察 |
只有在改变节点数据时才会观察到,如果你删除或者增加节点都不会进行观察,还有如果对不是CharacterData类型的节点的改变不会观察到
TIP
CharacterData 抽象接口(abstract interface)代表 Node 对象包含的字符。这是一个抽象接口,意味着没有 CharacterData 类型的对象。 它是在其他接口中被实现的,如 Text、Comment 或 ProcessingInstruction (en-US) 这些非抽象接口。
- 观察属性
observer.observe(document.body, { attributeFilter:['foo'] });//只监听属性foo的变化
observer.observe(document.body, { attributeOldValue: true});//可在MutationRecord中oldVlaue获取旧值
- 观察字符数据,MutationObserver可以观察文本节点(text、comment等)中的添加、删除、修改
let k=document.getElementById("k")
let observer = new MutationObserver((mutationrecords) => {
console.log(mutationrecords)
});
observer.observe(k.firstChild, { characterDataOldValue:true});
k.firstChild.textContent="K1922238432432883"
console.log('Changed body class');
- 观察子节点 可以观察目标节点的子节点的添加和移除
let k=document.getElementById("k")
let observer = new MutationObserver((mutationrecords) => {
console.log(mutationrecords)
});
observer.observe(k, { childList:true});
k.appendChild(document.createElement("p"))
k.appendChild(document.createElement("span"))
k.insertBefore(k.lastChild,k.firstChild)//对子节点重新排序(尽管调用一个方法即可实现)会报告两次,先移除再增加!
console.log('Changed body class');
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<div id='target' class='block' name='target'>
target的第一个子节点
<p>
<span>target的后代</span>
</p>
</div>
</body>
</html>
<script type="text/javascript">
var target=document.getElementById('target');
var i=0
var observe=new MutationObserver(function (mutations,observe) {
console.log(mutations);// [MutationRecord, MutationRecord]
});
observe.observe(target,{ childList: true});
target.appendChild(document.createTextNode('新增Text节点')); //增加节点,观察到变化
target.childNodes[0].remove(); //删除节点,可以观察到
target.childNodes[0].textContent='改变子节点的后代'; //不会观察到
</script>
- 观察子树 观察子节点及其后代,注意:当子树的节点移除子树,仍然能触发事件的监听
let observer = new MutationObserver((mutationrecords) => {
console.log(mutationrecords)
});
observer.observe(document.body, { attributes:true,subtree:true});
document.getElementById("k").setAttribute("foo","t1")
let span=document.createElement("span")
document.getElementById("k").appendChild(span)
span.setAttribute("t1","skt")
k.removeChild(span)
span.setAttribute("t1","skt1")
//(3) [MutationRecord, MutationRecord, MutationRecord]
# takeRecords()
清空队列并返回其中所有的MutationRecord实例,同时也不会像disconnect那样直接舍弃
let observer = new MutationObserver((mutationrecords) => {
console.log(mutationrecords)
});
observer.observe(document.body, { attributes:true});
document.body.className="k1"
document.body.className="k2"
document.body.className="k3"
var a=observer.takeRecords()
console.log(a)//(3) [MutationRecord, MutationRecord, MutationRecord]
var b=observer.takeRecords()
console.log(b)//[]
# ResizeObserver
应用场景:监听一个div是否发生宽度变化
const ro = new ResizeObserver((entries, observer) => {
console.log(entries,observer)
for (let entry of entries) {
console.log('Element:', entry.target);
console.log('Element size:', entry.contentRect);
}
});
const div = document.querySelector('#myDiv');
ro.observe(div);