Mutation Observer API

    概念上,它很接近事件,可以理解为 DOM 发生变动就会触发 Mutation Observer 事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发。

    这样设计是为了应付 DOM 变动频繁的特点。举例来说,如果文档中连续插入1000个元素,就会连续触发1000个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;而 Mutation Observer 完全不同,只在1000个段落都插入结束后才会触发,而且只触发一次。

    Mutation Observer 有以下特点。

    • 它等待所有脚本任务完成后,才会运行(即异步触发方式)。
    • 它把 DOM 变动记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变动。
    • 它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。

    MutationObserver 构造函数

    使用时,首先使用MutationObserver构造函数,新建一个观察器实例,同时指定这个实例的回调函数。

    上面代码中的回调函数,会在每次 DOM 变动后调用。该回调函数接受两个参数,第一个是变动数组,第二个是观察器实例,下面是一个例子。

    1. var observer = new MutationObserver(function (mutations, observer) {
    2. mutations.forEach(function(mutation) {
    3. console.log(mutation);
    4. });
    5. });

    observe()方法用来启动监听,它接受两个参数。

    • 第一个参数:所要观察的 DOM 节点
    • 第二个参数:一个配置对象,指定所要观察的特定变动
    1. var article = document.querySelector('article');
    2. var options = {
    3. 'childList': true,
    4. 'attributes':true
    5. } ;
    6. observer.observe(article, options);

    上面代码中,observe()方法接受两个参数,第一个是所要观察的DOM元素是article,第二个是所要观察的变动类型(子节点变动和属性变动)。

    • childList:子节点的变动(指新增,删除或者更改)。
    • attributes:属性的变动。
    • characterData:节点内容或节点文本的变动。

    想要观察哪一种变动类型,就在option对象中指定它的值为true。需要注意的是,至少必须同时指定这三种观察的一种,若均未指定将报错。

    除了变动类型,options对象还可以设定以下属性:

    • subtree:布尔值,表示是否将该观察器应用于该节点的所有后代节点。
    • attributeOldValue:布尔值,表示观察attributes变动时,是否需要记录变动前的属性值。
    • characterDataOldValue:布尔值,表示观察characterData变动时,是否需要记录变动前的值。
    • attributeFilter:数组,表示需要观察的特定属性(比如['class','src'])。
    1. // 开始监听文档根节点(即<html>标签)的变动
    2. mutationObserver.observe(document.documentElement, {
    3. attributes: true,
    4. characterData: true,
    5. childList: true,
    6. subtree: true,
    7. attributeOldValue: true,
    8. characterDataOldValue: true
    9. });

    对一个节点添加观察器,就像使用addEventListener()方法一样,多次添加同一个观察器是无效的,回调函数依然只会触发一次。如果指定不同的options对象,以后面添加的那个为准,类似覆盖。

    下面的例子是观察新增的子节点。

    disconnect()方法用来停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器。

    1. observer.disconnect();

    takeRecords()方法用来清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组。

      下面是一个例子。

      1. // 保存所有没有被观察器处理的变动
      2. var changes = mutationObserver.takeRecords();
      3. // 停止观察
      4. mutationObserver.disconnect();

      MutationRecord 对象

      对象包含了DOM的相关信息,有如下属性:

      • type:观察的变动类型(attributescharacterData或者childList)。
      • target:发生变动的DOM节点。
      • addedNodes:新增的DOM节点。
      • removedNodes:删除的DOM节点。
      • previousSibling:前一个同级节点,如果没有则返回null
      • nextSibling:下一个同级节点,如果没有则返回null
      • attributeName:发生变动的属性。如果设置了attributeFilter,则只返回预先指定的属性。
      • oldValue:变动前的值。这个属性只对attributecharacterData变动有效,如果发生childList变动,则返回null

      下面的例子说明如何读取变动记录。

      上面代码的观察器,观察<body>的所有下级节点(childList表示观察子节点,subtree表示观察后代节点)的变动。回调函数会在控制台显示所有变动的类型和目标节点。

      下面的例子说明如何追踪属性的变动。

      1. var callback = function (records) {
      2. records.map(function (record) {
      3. console.log('Previous attribute value: ' + record.oldValue);
      4. });
      5. };
      6. var mo = new MutationObserver(callback);
      7. var element = document.getElementById('#my_element');
      8. var options = {
      9. 'attributes': true,
      10. 'attributeOldValue': true
      11. }
      12. mo.observe(element, options);

      上面代码先设定追踪属性变动('attributes': true),然后设定记录变动前的值。实际发生变动时,会将变动前的值显示在控制台。

      网页加载的时候,DOM 节点的生成会产生变动记录,因此只要观察 DOM 的变动,就能在第一时间触发相关事件,也就没有必要使用DOMContentLoaded事件。

      1. var observer = new MutationObserver(callback);
      2. observer.observe(document.documentElement, {
      3. childList: true,
      4. subtree: true
      5. });

      上面代码中,监听document.documentElement(即网页的<html>HTML 节点)的子节点的变动,subtree属性指定监听还包括后代节点。因此,任意一个网页元素一旦生成,就能立刻被监听到。

      1. (function(win){
      2. var listeners = [];
      3. var doc = win.document;
      4. var MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
      5. var observer;
      6. function ready(selector, fn){
      7. // 储存选择器和回调函数
      8. listeners.push({
      9. selector: selector,
      10. });
      11. if(!observer){
      12. // 监听document变化
      13. observer = new MutationObserver(check);
      14. observer.observe(doc.documentElement, {
      15. childList: true,
      16. subtree: true
      17. });
      18. }
      19. // 检查该节点是否已经在DOM中
      20. check();
      21. }
      22. function check(){
      23. // 检查是否匹配已储存的节点
      24. for(var i = 0; i < listeners.length; i++){
      25. var listener = listeners[i];
      26. // 检查指定节点是否有匹配
      27. var elements = doc.querySelectorAll(listener.selector);
      28. for(var j = 0; j < elements.length; j++){
      29. var element = elements[j];
      30. // 确保回调函数只会对该元素调用一次
      31. if(!element.ready){
      32. element.ready = true;
      33. // 对该节点调用回调函数
      34. listener.fn.call(element, element);
      35. }
      36. }
      37. }
      38. }
      39. // 对外暴露ready
      40. win.ready = ready;
      41. })(this);
      42. // 使用方法
      43. ready('.foo', function(element){
      44. });

      参考链接