Mutation Observer API 突变监视接口
用来监视 DOM 变动。
DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知
概念上,它很接近事件,可以理解为 DOM 发生变动就会触发 Mutation Observer 事件。
但是,Mutation Observer 与 事件 有一个本质不同:
-
- 事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;
- Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发
Mutation Observer 有以下特点:
-
- 它等待所有脚本任务完成后,才会运行(即异步触发方式)。
- 它把 DOM 变动记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变动。
- 它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。
Mutation Observer 构造函数
- 使用
- 使用 MutationObserver 构造函数,新建一个观察器实例,同时指定这个实例的回调函数。
-
var box = document.getElementById("test_box");
// 1. 定义一个 监视器 实例
var mo = new MutationObserver(function(mutations, observer){ mutations.forEach(function(mutation){ console.log(mutation); }); });
// 2. 定义要监听的 类型集合 对象
var chgType = {
"childList": true, // 子节点变动 监视
"attributes": true, // 属性变动 监视
"characterData": true, // 节点内容 或者 节点文本 的变动
};
// 3. 启动监听
mo.observe(box, chgType);
-
- 使用 MutationObserver 构造函数,新建一个观察器实例,同时指定这个实例的回调函数。
.observe() 启动监视器
- 第一个参数是 所要观察的DOM元素是article
- 第二个参数是 所要观察的变动类型
至少指定一种要监听的变动类型,否则报错
"childList": true, 子节点 变动
"attributes": true, 属性 变动
"characterData": true, 节点内容 或者 节点文本 变动
还可指定监听属性
"subtree": true, 是否同时监视该节点的所有后代节点
"attributeOldValue": true, 监视 attributes 变动时,是否需要记录变动前的属性值
"characterDataOldValue": true, 监视 characterData 变动时,是否需要记录变动前的属性值
"attributeFilter": 数组, 表示需要观察的特定属性(比如['class','src'])
- 实例:观察新增的子节点
-
var insertedNodes = [];
var mo = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { for (var i = 0; i < mutation.addedNodes.length; i++) insertedNodes.push(mutation.addedNodes[i]); }) });
mo.observe(document, { childList: true }); console.log(insertedNodes);
.disconnect(); 停止监视
.takeRecords(); 清除变动记录,即不再处理未处理的变动____该方法返回变动记录的数组
-
// 保存所有没有被观察器处理的变动 var changes = mutationObserver.takeRecords();
MutationRecord 对象
DOM 每次发生变化,就会生成一条变动记录(MutationRecord 实例)。该实例包含了与变动相关的所有信息。
Mutation Observer 处理的就是一个个 MutationRecord 实例所组成的数组
- MutationRecord 对象包含了 DOM 的相关信息,有如下属性:
type 观察的 突变类型(attributes、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 chgType = { 'childList': true, 'subtree': true }; mo.observe(document.body, chgType);
- 实例: 属性变动('attributes': true), 实际发生变动时,会将变动前的值显示在控制台。
-
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);
- 封装: 目标元素 只要在 DOM 上已加载, 则执行 fn
-
(function(win){ 'use strict'; var doc = win.document; var MutationObserver = win.MutationObserver || win.WebKitMutationObserver; var targets = []; var mo; function isReadyOrNot(){ // 检查是否匹配已储存的节点 for(var i = 0; i < targets.length; i++){ var target = targets[i]; var elements = doc.querySelectorAll(target.selector); // 检查指定节点是否有匹配 for(var j = 0; j < elements.length; j++){ var element = elements[j]; // 确保回调函数只会对该元素调用一次 if(!element.isReady){ element.isReady = true; // 对该节点调用回调函数 target.fn.call(element, element); }; }; }; }; /**** 目标元素 只要在 DOM 上已加载, 则执行 fn ****/ win.eleReady = function(selector, fn){ // 储存选择器和回调函数 targets.push({ selector: selector, fn: fn }); if(!mo){ mo = new MutationObserver(isReadyOrNot); // 定义 突变监听器 mo.observe(doc.documentElement, { // 开始监听 document变化 childList: true, // 监听 子节点 subtree: true // 同时监听 后代节点 }); }; isReadyOrNot(); // 检查该节点是否已经在DOM中 }; })(window); eleReady('.foo', function(element){ // ... });