目录搜索
导论前言为什么学习JavaScriptJavaScript的历史基本语法语法概述数值字符串对象数组函数运算符数据类型转换错误处理机制JavaScript 编程风格标准库Object对象包装对象和Boolean对象Number对象String对象Math对象Date对象RegExp对象JSON对象ArrayBuffer:类型化数组面向对象编程概述封装继承模块化编程DOMNode节点document节点Element对象Text节点和DocumentFragment节点Event对象CSS操作Mutation Observer浏览器对象浏览器的JavaScript引擎定时器window对象history对象Ajax同域限制和window.postMessage方法Web Storage:浏览器端数据储存机制IndexedDB:浏览器端数据库Web Notifications APIPerformance API移动设备APIHTML网页元素Canvas APISVG 图像表单文件和二进制数据的操作Web Worker服务器发送事件Page Visibility APIFullscreen API:全屏操作Web SpeechrequestAnimationFrameWebSocketWebRTCWeb ComponentsHTML网页的APIHTML网页元素Canvas APISVG 图像表单文件和二进制数据的操作Web Worker服务器发送事件Page Visibility APIFullscreen API:全屏操作Web SpeechrequestAnimationFrameWebSocketWebRTCWeb Components开发工具console对象PhantomJSBower:客户端库管理工具Grunt:任务自动管理工具Gulp:任务自动管理工具Browserify:浏览器加载Node.js模块RequireJS和AMD规范Source MapJavaScript 程序测试JavaScript高级语法Promise对象有限状态机MVC框架与Backbone.js严格模式ECMAScript 6 介绍附录JavaScript API列表
文字

    • 概述

    • MutationObserver构造函数

    • Mutation Observer实例的方法

      • observe()

      • disconnect(),takeRecords()

      • MutationRecord对象

    • 应用示例

      • 子元素的变动

      • 属性的变动

      • 取代DOMContentLoaded事件

    • 参考链接

概述

Mutation Observer(变动观察器)是监视DOM变动的接口。DOM发生任何变动,Mutation Observer会得到通知。

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

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

Mutation Observer有以下特点:

  • 它等待所有脚本任务完成后,才会运行,即采用异步方式。

  • 它把DOM变动记录封装成一个数组进行处理,而不是一条条地个别处理DOM变动。

  • 它既可以观察发生在DOM的所有类型变动,也可以观察某一类变动。

目前,Firefox(14+)、 Chrome(26+)、Opera(15+)、IE(11+)和Safari(6.1+)支持这个API。Safari 6.0和Chrome 18-25使用这个API的时候,需要加上WebKit前缀(WebKitMutationObserver)。可以使用下面的表达式,检查当前浏览器是否支持这个API。

var MutationObserver = window.MutationObserver
  || window.WebKitMutationObserver
  || window.MozMutationObserver;

var observeMutationSupport = !!MutationObserver;

MutationObserver构造函数

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

var observer = new MutationObserver(callback);

观察器的回调函数会在每次DOM发生变动后调用。它接受两个参数,第一个是变动数组(详见后文),第二个是观察器实例。

Mutation Observer实例的方法

observe()

observe方法指定所要观察的DOM节点,以及所要观察的特定变动。

var article = document.querySelector('article');

var  options = {
  'childList': true,
  'attributes':true
} ;

observer.observe(article, options);

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

观察器所能观察的DOM变动类型(即上面代码的options对象),有以下几种:

  • childList:子节点的变动。

  • attributes:属性的变动。

  • characterData:节点内容或节点文本的变动。

  • subtree:所有后代节点的变动。

想要观察哪一种变动类型,就在option对象中指定它的值为true。需要注意的是,不能单独观察subtree变动,必须同时指定childList、attributes和characterData中的一种或多种。

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

  • attributeOldValue:类型为布尔值,表示观察attributes变动时,是否需要记录变动前的属性值。

  • characterDataOldValue:类型为布尔值,表示观察characterData变动时,是否需要记录变动前的值。

  • attributeFilter:类型为数组,表示需要观察的特定属性(比如['class','src'])。

对一个节点添加观察器,就像添加addEventListener方法一样。多次添加同一个观察器是无效的,回调函数依然只会触发一次。但是,如果指定不同的options对象,就会被当作两个不同的观察器。

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

var insertedNodes = [];
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    for (var i = 0; i < mutation.addedNodes.length; i++)
      insertedNodes.push(mutation.addedNodes[i]);
  })
});
observer.observe(document, { childList: true });
console.log(insertedNodes);

disconnect(),takeRecords()

disconnect方法用来停止观察。再发生相应变动,就不再调用回调函数。

observer.disconnect();

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

observer.takeRecords();

MutationRecord对象

DOM每次发生变化,就会生成一条变动记录。这个变动记录对应一个MutationRecord对象,该对象包含了与变动相关的所有信息。Mutation Observer处理的是一个个MutationRecord对象所组成的数组。

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

  • type:观察的变动类型(attribute、characterData或者childList)。

  • target:发生变动的DOM节点。

  • addedNodes:新增的DOM节点。

  • removedNodes:删除的DOM节点。

  • previousSibling:前一个同级节点,如果没有则返回null。

  • nextSibling:下一个同级节点,如果没有则返回null。

  • attributeName:发生变动的属性。如果设置了attributeFilter,则只返回预先指定的属性。

  • oldValue:变动前的值。这个属性只对attribute和characterData变动有效,如果发生childList变动,则返回null。

应用示例

子元素的变动

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

var callback = function(records){
  records.map(function(record){
    console.log('Mutation type: ' + record.type);
    console.log('Mutation target: ' + record.target);
  });
};

var mo = new MutationObserver(callback);

var option = {
  'childList': true,
  'subtree': true
};

mo.observe(document.body, option);

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

属性的变动

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

var callback = function(records){
  records.map(function(record){
    console.log('Previous attribute value: ' + record.oldValue);
  });
};

var mo = new MutationObserver(callback);

var element = document.getElementById('#my_element');

var options = {
  'attributes': true,
  'attributeOldValue': true
}

mo.observe(element, options);

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

取代DOMContentLoaded事件

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

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

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

下面的代码,使用MutationObserver对象封装一个监听DOM生成的函数。

(function(win){
  'use strict';

  var listeners = [];
  var doc = win.document;
  var MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
  var observer;

  function ready(selector, fn){
    // 储存选择器和回调函数
    listeners.push({
      selector: selector,
      fn: fn
    });
    if(!observer){
      // 监听document变化
      observer = new MutationObserver(check);
      observer.observe(doc.documentElement, {
        childList: true,
        subtree: true
      });
    }
    // 检查该节点是否已经在DOM中
    check();
  }

  function check(){
  // 检查是否匹配已储存的节点
    for(var i = 0; i < listeners.length; i++){
      var listener = listeners[i];
      // 检查指定节点是否有匹配
      var elements = doc.querySelectorAll(listener.selector);
      for(var j = 0; j < elements.length; j++){
        var element = elements[j];
        // 确保回调函数只会对该元素调用一次
        if(!element.ready){
          element.ready = true;
          // 对该节点调用回调函数
          listener.fn.call(element, element);
        }
      }
    }
  }

  // 对外暴露ready
  win.ready = ready;

})(this);

ready('.foo', function(element){
  // ...
});

参考链接


上一篇:下一篇: