监听DOM变动,用MutationObserver应该很香。

3,918 阅读4分钟

1.DOM树变动?

    浏览器中DOM节点的属性,文本,插入,移除等结构的变动我们之前主要是通过监听Mutation Events事件来执行

1.1Mutation 事件列表

  • DOMAttrModified(节点属性变更)
  • DOMAttributeNameChanged(节点属性属性节点名字改变)
  • DOMCharacterDataModified(节点中的文本节点改变)
  • DOMElementNameChanged(节点移除)
  • DOMNodeInserted(节点子节点插入)
  • DOMNodeRemoved(节点子节点移除)
  • DOMNodeInsertedIntoDocument(节点插入文档)
  • DOMSubtreeModified(节点子节点修改)

1.2Mutation 简单的用法如下

document.getElementById('list').addEventListener("DOMSubtreeModified", function(){
  console.log('列表中子元素被修改');
}, false);

1.3Mutation Events遇到的问题

  • 浏览器兼容性问题
    1. IE9不支持Mutation Events
    2. Webkit内核不支持DOMAttrModified特性,
    3. DOMElementNameChangedDOMAttributeNameChanged 在Firefox上不被支持。
  • 性能问题
    1. Mutation Events是同步执行的,它的每次调用,都需要从事件队列中取出事件,执行,然后事件队列中移除,期间需要移动队列元素。如果事件触发的较为频繁的话,每一次都需要执行上面的这些步骤,那么浏览器会被拖慢。
    2. Mutation Events本身是事件,所以捕获是采用的是事件冒泡的形式,如果冒泡捕获期间又触发了其他的MutationEvents的话,很有可能就会导致阻塞Javascript线程,甚至导致浏览器崩溃。

2.Mutation Observer

    Mutation Observer 是在DOM4中定义的,用于替代 mutation events 的新API,它的不同于events的是,所有监听操作以及相应处理都是在其他脚本执行完成之后异步执行的,并且是所以变动触发之后,将变得记录在数组中,统一进行回调的,也就是说,当你使用observer监听多个DOM变化时,并且这若干个DOM发生了变化,那么observer会将变化记录到变化数组中,等待一起都结束了,然后一次性的从变化数组中执行其对应的回调函数。

1767848-e910b39375fedf30.webp

2.1使用方式

  • 构造函数(MutationObserver)     用来实例化一个Mutation观察者对象,其中的参数是一个回调函数,它是会在指定的DOM节点发送变化后,执行的函数,并且会被传入两个参数。一个是变化记录数组(MutationRecord),另一个是观察者对象本身。
/*
*观察者实例
*/
var observer = null;

/*
*观察者回调
*records:变化记录数组
*instance:观察者对象本身
*/
const callback = (records, instance) => {
    console.log(records)
    console.log(instance)
    records.map(record => {
      console.log('Mutation Type: ' + record.type)
      console.log('Mutation Change Attribute: ' + record.attributeName)
      console.log('Previous attribute value: ' + record.oldValue)
    })
}

if(window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver){
    observer = new MutationObserver(callback)
}
  • 观察者(observe) 在观察者对象上,注册需要观察的DOM节点,以及相应的参数
//观察者配置项
var options = { 
    childList: true,//观察目标节点的子节点的新增和删除 
    subtree: true, //观察目标节点的所有后代节点
    attributes: true, //观察目标节点的属性节点
    attributeOldValue: true,  //在attributes属性已经设为true的前提下, 将发生变化的属性节点之前的属性值记录下来
    attributeFilter:[],//一个属性名数组(不需要指定命名空间),只有该数组中包含的属性名发生变化时才会被观察到,其他名称的属性发生变化后会被忽略想要设置那些删选参数的话
    characterData: true,  //如果目标节点为characterData节点(一种抽象接口,具体可以为文本节点,注释节点,以及处理指令节点)时,也要观察该节点的文本内容是否发生变化
    characterDataOldValue:true,//在characterData属性已经设为true的前提下,将发生变化characterData节点之前的文本内容记录下来(记录到下面MutationRecord对象的oldValue属性中)
}

//待观察的DOM节点
var element = document.getElementById('text');

//执行观察
observer.observe(element, options)
  • 停止观察(disconnect) 在观察者对象上停止的节点的变化监听,直到重新调用observe方法
// 随后,你还可以停止观察
observer.disconnect();
  • 清除变动记录(takeRecords) 用来清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组
// 返回变动记录的数组,同时清除变动记录,即不再处理未处理的变动。
var changes = observer.takeRecords();

2.2应用场景之vue.$nextTick

vue中的$nextTick理解为DOM的下次更新后执行的回调,接受2个参数(回调函数和执行回调函数的上下文环境),如果没有提供回调函数,那么将返回promise对象。查看vue源码后发现在浏览器不支持porimise的情况,会首选MutationObserver进行微任务执行来实现DOM的异步更新

EA78409F-3F5B-4ae5-873E-E9B55BBCD476.png

3.相关连接