Mutation Observer 页面变动的监测方法详解

MutationObserver(以下简称MO)是DOM提供的标准接口,如果不是因为业务需求驱动,很少会触碰的一个功能。

MO的基础功能是,观察监视浏览器中的DOM节点并对DOM的变化做出响应。W3C将MutationEvents弃用,并用MutationObserver来替代。
可以看出,文档变动不应该被视为DOM的某项标准事件,倒是可以被视为DOM对象的绑定特性。

MO的功能实际上对开发是大有帮助的,它使得我们从代码层面上对DOM掌控能力增强了。

作为一个标准,也是要落地的,从目前浏览器的支持情况来看,令人满意。

http://caniuse.com/#search=Mutationobserver
http://caniuse.com/#search=Mutationobserver

这里需要关注的有两点:
1. 在Android 4.4之前的浏览器中该功能是不被支持的,这会影响一部分用户(结合国情)
2. 当改变一个节点的innerHTML属性时,如果innerHTML包含一个CharacterData节点,WebKit和其他浏览器的处理方式是不同的,在WebKit浏览器中会认为这是当前节点的characterData变动,而其他浏览器认为这是这个节点父级节点的一个childList变动。

可能以上第2点会难以理解。带着这个困惑熟悉一下MutationObserver。事实上在了解之前,最好熟悉一下DOM和DOM节点的基础知识,这里就不展开了。
1. MutationObserver 构造器

new MutationObserver(
  function callback
);

callback是它的参数,是触发DOM变动时的回调函数。回调发生时,会向callback传入2个对象:
第1个是变动的对象,是MutationRecord类型的对象实例。
第2个是mutationObserver的实例。
所以,我们在创建MO之前,先创建一个callback。

  1. MutationObserver的CallBack Function
var callback = function(mutationRecords, observerInstance) {
    // mutationRecords 是一个数组,可以通过forEach等方法遍历
    // observerInstance 是MO的实例,通常很少用到
}

var mo = new MutationObserver(callback);
  1. 绑定DOM
    以上只完成了MO接口对象的实例化,还需要绑定DOM才能真正实现监控。
    mo对象提供了监控和停止监控两个方法,以及获取监控记录,看字面比较好理解。
//开启监控
mo.observe(target, config);

//停止监控
mo.disconnect();

//获取已存在的监控记录,返回MutationRecords Array
mo.takeRecords();

mo.observer(target, config); 接收两个参数:
– target:必传,是需要监控的DOM对象,比如document.body
– config:选传。虽然是可选配置参数,但config帮助mo明确监控的范围(建议再次强化DOM元素的知识)

var config = {
childList:true, //设置为true表示监听指定元素的子元素的添加/移除变动(包括text node 类型节点);
attributes: true, //设置为true表示监听指定元素的属性的变动;
characterData: true, //设置为true表示监听指定元素的data数据变动;
subtree: true, //设置为true表示不仅监听目标元素,也同时监听其子元素变动;
attributeOldValue:true, //在attributes属性已经设为true的前提下,如果需要将发生变化的属性节点之前的属性值记录下来(记录到下面MutationRecord对象的oldValue属性中),则设置为true;
characterDataOldValue:true, //在characterData属性已经设为true的前提下,如果需要将发生变化的characterData节点之前的文本内容记录下来(记录到下面MutationRecord对象的oldValue属性中),则设置为true.
attributeFilter:true, 一个属性名数组(不需要指定命名空间),只有该数组中包含的属性名发生变化时才会被观察到,其他名称的属性发生变化后会被忽略.
}

这么来看,通过config可以更有针对性地监看DOM对象的某个节点的某些特征,比如attributeFilter可以将DOM变化精确到某个属性。

讲到这里,我们已经可以利用以上代码实现DOM变化监测,建议打开console,查看callback的第一个返回值。
返回值为MutationRecord对象类型,MutationRecord数组中的每项就是DOM产生的每次变化。

//以下是一段伪代码,console输出MutationRecord数组中的某一项。
{
    addedNodes:NodeList, //添加的节点
    attributeName:null, //变化的属性名
    attributeNamespace:null, //变化的命名空间
    data:null, //变化的数据
    nextSibling:null, //后置相邻节点
    oldValue:null, //变化之前的数据值
    previousSibling:null, //前置相邻节点
    removedNodes:NodeList, //移除的节点
    target:a, //发生变化的对象
    type:"characterData" //变化类型
}

通过type属性可以判断当前节点发生了怎样的变化,属性值与注册监控时的config参数内容对应。

这时,回顾一下前面产生的疑惑:

如果innerHTML包含一个CharacterData节点,WebKit和其他浏览器的处理方式是不同的。

举个栗子即:

<a>Links</a>

当改变a标签包裹的内容时,对A标签而言:
“Links”是innerHTML的值,
“Links”也被视作是一类节点————TextNode (NodeType:3)
浏览器内核处理此类情况表现的差异,也导致了MutationRecord得到的类型不同。

  1. 绑定监控的时机
    当浏览器载入HTML信息,文档开始加载时,DOM的变化已经发生了。
    根据实际业务需求,可以考虑在DOM开始加载时、DOM加载后、页面加载完成后对文档进行监控。

  2. 兼容性
    使用接口对象时需要考虑兼容性,在早期版本中使用该特性需要加入前缀。

const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
if(MutationObserver) {
    // ......
}

MutationObserver功能就已经介绍完了,多少会让人回味。DOM是前端的必修课之一,其丰富的内容让人回味不绝,经常拿出来品尝,虽不能果腹,但有种甜点的感觉。

参考:
WHATWG-DOM标准说明链接
MDN API文档链接
config的配置释义链接

发表评论

电子邮件地址不会被公开。 必填项已用*标注