MutationObserver用法总结( 监听DOM变化 )

16,564 阅读5分钟

概述

  • Mutation Observer是用于代替Mutation events作为观察DOM树结构发生变化时,做出相应处理的API;
  • 为什么要使用Mutation Observer去代替Mutation Events呢?我们先了解一下Mutation Events

Mutation Events

  • Mutation Events是在DOM3中定义的,用于监听DOM树结构变化的事件;
  • Mutation Events列表:
    • DOMAttrModified
    • DOMAttributeNameChanged
    • DOMCharacterDataModified
    • DOMElementNameChanged
    • DOMNodeInserted
    • DOMNodeRemoved
    • DOMNodeInsertedIntoDocument
    • DOMSubtreeModified
    • 其中DOMNodeRemovedDOMNodeInsertedDOMSubtreeModified分别用于监听元素子项的删除、新增、修改(包括删除和新增);
    • DOMAttrModified是监听元素属性的修改,并且能够提供具体的修改动作;
  • 用法如下:
    document.getElementById('list').addEventListener("DOMSubtreeModified", function(){
      console.log('列表中子元素被修改');
    }, false);
    
  • Mutation Events遇到的问题:
    • 浏览器兼容性问题:
      • IE9不支持Mutation Events
      • Webkit内核不支持DOMAttrModified特性;
      • DOMElementNameChangedDOMAttributeNameChangedFirefox上不被支持;
    • 性能问题:
      • Mutation Events是同步执行的:
        • 它的每次调用,都需要从事件队列中取出事件并执行,然后事件队列中移除,期间需要移动队列元素;
        • 如果事件触发的较为频繁的话,每一次都需要执行上面的这些步骤,那么浏览器会被拖慢;
      • Mutation Events本身是事件,所以捕获是采用的是事件冒泡的形式;
        • 如果冒泡捕获期间又触发了其他的Mutation Events的话,很有可能就会导致阻塞Javascript线程,甚至导致浏览器崩溃;

Mutation Observer

  • Mutation Observer是在DOM4中定义的,用于替代Mutation Events的新API

  • Mutation ObserverMutation Events不同的是:

    • 所有监听操作以及相应处理都是在其他脚本执行完成之后异步执行的;
    • 并且是所以变动触发之后,将变动记录在数组中,统一进行回调的;
      • 即当你使用Mutation Observer监听多个DOM变化时,并且这些DOM发生了变化,那么Mutation Observer会将变化记录到变化数组中,等都结束了,再一次性的从变化数组中执行其对应的回调函数;
  • Mutation Observer中得方法(可通过console.dir(MutationObserver)查看): image.png

    1. observe

      observe(target, config);
      target:需要监听的元素[element]
      config:需要监听的属性[Object],例如attributes、childList等

      let box = document.querySelector('#box'),
        config = { attributes: true };
      
      let observer = new MutationObserver(mutations => {
        console.log(mutations);
        // => 返回一个监听到的MutationRecord对象
        // MutationRecord对象是每修改一个就会在数组里面追加一个
      });
      
      observer.observe(box, config); // 监听的box元素和config配置项
      box.setAttribute('name','张三'); // 修改属性
      box.style.background = 'black'; // 修改样式
      
      • 运行上述代码,输出结果为一个集合 集合中有两个MutationRecord对象: image.png
      • MutationRecord对象的属性:
        image.png
        • type
          • 如果是属性发生变化,则返回attributes;
          • 如果是一个CharacterData节点发生变化,则返回characterData;
          • 如果是目标节点的某个子节点发生了变化,则返回childList;
        • target:返回此次变化影响到的节点,具体返回那种节点类型是根据type值的不同而不同的;
          • 如果type为attributes,则返回发生变化的属性节点所在的元素节点;
          • 如果type值为characterData,则返回发生变化的这个characterData节点;
          • 如果type为childList,则返回发生变化的子节点的父节点;
        • addedNodes:返回被添加的节点;
        • removedNodes:返回被删除的节点;
        • previousSibling:返回被添加或被删除的节点的前一个兄弟节点;
        • nextSibling:返回被添加或被删除的节点的后一个兄弟节点;
        • attributeName:返回变更属性的本地名称;
        • oldValue:根据type值的不同,返回的值也会不同;
          • 如果type为attributes,则返回该属性变化之前的属性值;
          • 如果type为characterData,则返回该节点变化之前的文本数据;
          • 如果type为childList,则返回null;
      • observe函数的第二个参数config的配置项:
        属性说明
        childLIst观察目标节点的子节点的新增和删除;
        attributes观察目标节点的属性节点(新增或删除了某个属性,以及某个属性的属性值发生了变化);
        attributeFilter要监视的特定属性名称的数组,如果未包含此属性,则对所有属性的更改都会触发变动通知;
        attributeOldValueattributes属性已经设为true的前提下, 将发生变化的属性节点之前的属性值记录下来(记录在MutationRecord对象的oldValue属性中);
        characterData如果目标节点为characterData节点(一种抽象接口,具体可以为文本节点、注释节点,以及处理指令节点)时,也要观察该节点的文本内容是否发生变化;
        characterDataOldValuecharacterData属性已经设为true的前提下,将发生变化characterData节点之前的文本内容记录下来(记录在MutationRecord对象的oldValue属性中);
        subtree观察目标节点的所有后代节点(观察目标节点所包含的整棵DOM树上的上述三种节点变化);
    2. disconnect

      停止MutationObserver对象的观察,且清空所有的MutationRecord对象;

      let observer = new MutationObserver(mutations => {
        console.log(mutations);
      });
      
      observer.observe(box, config);
      box.setAttribute('name','张三');
      
      observer.disconnect();
      
      box.style.background = 'black';
      
      • 运行上述代码,查看控制台,什么都没有输出,这是为什么呢?
        • 这是因为在new MutationObserver传入的函数并不会监听到修改了就立即执行,而是等到同步代码都执行完了,才会去调用我们传入的回调函数;

    • takeRecords

      在观察者对象上调用takeRecords会返回其观察节点上的变化记录(MutationRecord)数组
      其中MutationRecord数组也会作为观察者初始化时的回调函数的第一个参数; 同时也会从MutationObserver的通知队列中删除所有待处理的通知; takeRecords方法是同步执行,可以即时获取;

      let observer = new MutationObserver(mutations => {
        console.log(mutations)
      })
      
      observer.observe(box, config)
      box.setAttribute('name', '张三')
      
      let arr = observer.takeRecords() 
      // 获取队列中未处理的,
      // 这个方法是同步执行,可以即时获取
      console.log(arr) 
      
      box.style.background = 'black'
      
      • 运行结果: image.png

查看原文:
MutationObserver 用法总结( 监听节点、DOM变化 )
MutationObserver 监听DOM树变化