# 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) 这些非抽象接口。

  1. 观察属性
observer.observe(document.body, { attributeFilter:['foo'] });//只监听属性foo的变化
observer.observe(document.body, { attributeOldValue: true});//可在MutationRecord中oldVlaue获取旧值
  1. 观察字符数据,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');
  1. 观察子节点 可以观察目标节点的子节点的添加和移除
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>
  1. 观察子树 观察子节点及其后代,注意:当子树的节点移除子树,仍然能触发事件的监听
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);

# IntersectionObserver 交叉观察器

链接

最后更新: 4/23/2024, 9:05:25 AM