(转载)了解 HTML5 中的 MutationObserver

MutationObserver 翻译过来就是变动观察器,字面上就可以理解这是用来观察 Node(节点)变化的。MutationObserver 是在 DOM4 规范中定义的,它的前身是 MutationEvent 事件,该事件最初在 DOM2 事件规范中介绍,到来了 DOM3 事件规范中正式定义,但是由于该事件存在兼容性以及性能上的问题被弃用。兼容性查阅

MutationObserver 是一个构造器,接受一个 callback 参数,用来处理节点变化的回调函数。返回两个参数:

  • mutations:节点变化记录列表(sequence<MutationRecord>
  • observer:构造 MutationObserver 对象
1
var observe = new MutationObserver(function (mutations, observer) {});

MutationObserver 对象有三个方法,分别如下:

  • observe:设置观察目标,接受两个参数: (target:观察目标,options:通过对象成员来设置观察选项)
  • disconnect:阻止观察者观察任何改变
  • takeRecords:清空记录队列并返回里面的内容

关于 observe 方法中 options 参数有已下几个选项:

  • childList:设置 true,表示观察目标子节点的变化,比如添加或者删除目标子节点,不包括修改子节点以及子节点后代的变化
  • attributes:设置 true,表示观察目标属性的改变
  • characterData:设置 true,表示观察目标数据的改变
  • subtree:设置为 true,目标以及目标的后代改变都会观察
  • attributeOldValue:如果属性为 true 或者省略,则相当于设置为 true,表示需要记录改变前的目标属性值,设置了 attributeOldValue 可以省略 attributes 设置
  • characterDataOldValue:如果 characterDatatrue 或省略,则相当于设置为 true,表示需要记录改变之前的目标数据,设置了 characterDataOldValue 可以省略 characterData 设置
  • attributeFilter:如果不是所有的属性改变都需要被观察,并且 attributes 设置为 true 或者被忽略,那么设置一个需要观察的属性本地名称(不需要命名空间)的列表

下表描述了 MutationObserver 选项与 MutationEvent 名称之间的对应关系:

MutationEvent MutationObserver options
DOMNodeInserted { childList: true, subtree: true }
DOMNodeRemoved { childList: true, subtree: true }
DOMSubtreeModified { childList: true, subtree: true }
DOMAttrModified { attributes: true, subtree: true }
DOMCharacterDataModified { characterData: true, subtree: true }

从上表我们也可以看出相比与 MutationEvent 而言 MutationObserver 极大地增加了灵活性,可以设置各种各样的选项来满足程序员对目标的观察。

我们简单看几个例子:

1
2
3
4
5
6
<div id="target" class="block" name="target">
target的第一个子节点
<p>
<span>target的后代</span>
</p>
</div>

1.callback 的回调次数

1
2
3
4
5
6
7
8
9
10
var target = document.getElementById('target');
var i = 0;
var observe = new MutationObserver(function (mutations, observe) {
i++;
});
observe.observe(target, { childList: true });
target.appendChild(docuemnt.createTextNode('1'));
target.appendChild(docuemnt.createTextNode('2'));
target.appendChild(docuemnt.createTextNode('3'));
console.log(i); //1

MutationObservercallback 回调函数是异步的,只有在全部 DOM 操作完成之后才会调用 callback。

2.当只设置{ childList: true}时,表示观察目标子节点的变化

1
2
3
4
5
6
7
8
9
10
var observe = new MutationObserver(function (mutations, observe) {
debugger;
console.log(mutations);
//observe.discount();
});

observe.observe(target, { childList: true });
target.appendChild(document.createTextNode('新增 Text 节点')); //增加节点,观察到变化
target.childNodes[0].remove(); //删除节点,可以观察到
target.childNodes[0].textContent = '改变子节点的后代'; //不会观察到

如果想要观察到子节点以及后代的变化需设置{childList: true, subtree: true}

attributes 选项用来观察目标属性的变化,用法类似与 childList,目标属性的删除添加以及修改都会被观察到。

3.我们需要注意的是 characterData 这个选项,它是用来观察 CharacterData 类型的节点的,只有在改变节点数据时才会观察到,如果你删除或者增加节点都不会进行观察,还有如果对不是 CharacterData 类型的节点的改变不会观察到,比如:

1
2
3
4
5
observe.observe(target, { characterData: true, subtree: true });
target.childNodes[0].textContent = '改变 Text 节点'; //观察到
target.childNodes[1].textContent = '改变 p 元素内容'; //不会观察到
target.appendChild(document.createTextNode('新增 Text 节点')); //不会观察到
target.childNodes[0].remove(); //删除 TEXT 节点也不会观察到

我们只需要记住只有对 CharacterData 类型的节点的数据改变才会被 characterDatatrue 的选项所观察到。

4.最后关注一个特别有用的选项 attributeFilter,这个选项主要是用来筛选要观察的属性,比如你只想观察目标 style 属性的变化,这时可以如下设置:

1
2
3
observe.observe(target, { attributeFilter: ['style'], subtree: true });
target.style = 'color:red'; //可以观察到
target.removeAttribute('name'); //删除 name 属性,无法观察到

disconnect 方法是用来阻止观察的,当你不再想观察目标节点的变化时可以调用 observe.disconnect() 方法来取消观察。

takeRecords 方法是用来取出记录队列中的记录。它的一个作用是,比如你对一个节点的操作你不想马上就做出反应,过段时间在显示改变了节点的内容。

1
2
3
4
5
6
7
8
9
10
11
var observe = new MutationObserver(function () {});
observe.observe(target, { childList: true });
target.appendChild(document.createTextNode('新增 Text 节点'));
var record = observe.takeRecords(); //此时 record 保存了改变记录列表
//当调用 takeRecords 方法时,记录队列被清空因此不会触发 MutationObserver 中的 callback 回调方法。
target.appendChild(document.createElement('span'));
observe.disconnect(); //停止对 target 的观察。
//MutationObserver 中的回调函数只有一个记录,只记录了新增 span 元素

//之后可以对 record 进行操作
//...

MutationRecord 变动记录中的属性如下:

  • type:如果是属性变化,返回”attributes”,如果是一个 CharacterData 节点(Text 节点、Comment 节点)变化,返回”characterData”,节点树变化返回”childList”
  • target:返回影响改变的节点
  • addedNodes:返回添加的节点列表
  • removedNodes:返回删除的节点列表
  • previousSibling:返回分别添加或删除的节点的上一个兄弟节点,否则返回 null
  • nextSibling:返回分别添加或删除的节点的下一个兄弟节点,否则返回 null
  • attributeName:返回已更改属性的本地名称,否则返回 null
  • attributeNamespace:返回已更改属性的名称空间,否则返回 null
  • oldValue:返回值取决于 type。对于”attributes”,它是更改之前的属性的值。对于”characterData”,它是改变之前节点的数据。对于”childList”,它是 null

其中 typetarget 这两个属性不管是哪种观察方式都会有返回值,其他属性返回值与观察方式有关,比如只有当 attributeOldValue 或者 characterDataOldValuetrueoldValue 才有返回值,只有改变属性时,attributeName 才有返回值等。