[TOC]
## 概述
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){
// ...
});
~~~
## 参考链接
* Tiffany Brown, [Getting to know mutation observers](http://dev.opera.com/articles/view/mutation-observers-tutorial/)
* Michal Budzynski, [JavaScript: The less known parts. DOM Mutations](http://michalbe.blogspot.com/2013/04/javascript-less-known-parts-dom.html)
* Jeff Griffiths, [DOM MutationObserver – reacting to DOM changes without killing browser performance](https://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/)
* Addy Osmani, [Detect, Undo And Redo DOM Changes With Mutation Observers](http://addyosmani.com/blog/mutation-observers/)
* Ryan Morr, [Using Mutation Observers to Watch for Element Availability](http://ryanmorr.com/using-mutation-observers-to-watch-for-element-availability/)
- 第一章 导论
- 1.1 前言
- 1.2 为什么学习JavaScript?
- 1.3 JavaScript的历史
- 第二章 基本语法
- 2.1 语法概述
- 2.2 数值
- 2.3 字符串
- 2.4 对象
- 2.5 数组
- 2.6 函数
- 2.7 运算符
- 2.8 数据类型转换
- 2.9 错误处理机制
- 2.10 JavaScript 编程风格
- 第三章 标准库
- 3.1 Object对象
- 3.2 Array 对象
- 3.3 包装对象和Boolean对象
- 3.4 Number对象
- 3.5 String对象
- 3.6 Math对象
- 3.7 Date对象
- 3.8 RegExp对象
- 3.9 JSON对象
- 3.10 ArrayBuffer:类型化数组
- 第四章 面向对象编程
- 4.1 概述
- 4.2 封装
- 4.3 继承
- 4.4 模块化编程
- 第五章 DOM
- 5.1 Node节点
- 5.2 document节点
- 5.3 Element对象
- 5.4 Text节点和DocumentFragment节点
- 5.5 Event对象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 浏览器对象
- 6.1 浏览器的JavaScript引擎
- 6.2 定时器
- 6.3 window对象
- 6.4 history对象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:浏览器端数据储存机制
- 6.8 IndexedDB:浏览器端数据库
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移动设备API
- 第七章 HTML网页的API
- 7.1 HTML网页元素
- 7.2 Canvas API
- 7.3 SVG 图像
- 7.4 表单
- 7.5 文件和二进制数据的操作
- 7.6 Web Worker
- 7.7 SSE:服务器发送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 开发工具
- 8.1 console对象
- 8.2 PhantomJS
- 8.3 Bower:客户端库管理工具
- 8.4 Grunt:任务自动管理工具
- 8.5 Gulp:任务自动管理工具
- 8.6 Browserify:浏览器加载Node.js模块
- 8.7 RequireJS和AMD规范
- 8.8 Source Map
- 8.9 JavaScript 程序测试
- 第九章 JavaScript高级语法
- 9.1 Promise对象
- 9.2 有限状态机
- 9.3 MVC框架与Backbone.js
- 9.4 严格模式
- 9.5 ECMAScript 6 介绍
- 附录
- 10.1 JavaScript API列表
- 草稿一:函数库
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 设计模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件开发
- 12.4 jQuery.Deferred对象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS规范
- 13.3 package.json文件
- 13.4 npm模块管理器
- 13.5 fs 模块
- 13.6 Path模块
- 13.7 process对象
- 13.8 Buffer对象
- 13.9 Events模块
- 13.10 stream接口
- 13.11 Child Process模块
- 13.12 Http模块
- 13.13 assert 模块
- 13.14 Cluster模块
- 13.15 os模块
- 13.16 Net模块和DNS模块
- 13.17 Express框架
- 13.18 Koa 框架