MutationObserver API的概念与使用

301 阅读4分钟

1. 概述

MutationObserver是DOM3 Events规范的一部分,该接口提供了监视DOM树变更的能力,通常用于监视DOM节点class、属性等内容的变更

2. MutationObserver构造函数

MutationObserver() 构造函数——是 MutationObserver 接口内容的一部分——创建并返回一个新的观察器,语法如下

var observer = new MutationObserver(callback);

接收的参数为回调函数callback,每当被指定的节点或子树以及配置项有Dom变动时会被调用。回调函数拥有两个参数:一个是描述所有被触发改动的 MutationRecord 对象数组,另一个是调用该函数的MutationObserver 对象

我们可以通过第一个参数获取到发生变更的DOM节点的信息以及变更的信息,如下所示:

function callback(mutationList, observer) {
  mutationList.forEach((mutation) => {
    switch(mutation.type) {
      case 'childList':
        /* 从树上添加或移除一个或更多的子节点;参见 mutation.addedNodes 与
           mutation.removedNodes */
        break;
      case 'attributes':
        /* mutation.target 中某节点的一个属性值被更改;该属性名称在 mutation.attributeName 中,
           该属性之前的值为 mutation.oldValue */
        break;
    }
  });
}

使用 mutation.type获取发生的变动类别(无论是子节点的变动,还是节点属性的变动)。

3. observe方法

var observer = new MutationObserver(callback);

前面我们通过new MutationObserver(callback)获得了观察者observer对象了,此时可以开始监听DOM节点变化,通过下面的语句来开启该行为

observer.observe(target, options)

观察者对象上具有observe方法,接收2个参数。第1个参数target表示要观察变化的DOM节点,第2个参数options是可选的MutationObserverInit对象,该对象通过一些配置描述了被观察的DOM的哪些变化要触发当前观察者对象的callabck,具体示例如下:

const dom = document.querySelector('.container');
observer.observe(dom, {
  attributes: true,
  childList: true
})

这段代码表示要观察指定对象的属性变化和子节点变化(增加或删除子节点),当触发了这2个变化时,执行上方初始化观察者示例时传入的callback方法。

第2个参数除了可配置attributes(是否监听属性变化) 和 childList(是否监听子节点增加/删除)外,还支持下面这些选项

4. MutationObserverInit

observe方法的第2参数,还支持下面配置

attributeFilter

要监视的特定属性名称的数组。如果未包含此属性,则对所有属性的更改都会触发变动通知。

attributeOldValue

当监视节点的属性改动时,将此属性设为 true 将记录任何有改动的属性的上一个值 (通过MutationRecord对象的oldValue获取到上一个值)。当不指定该选项或设置为false则不会记录上一个值(通过MutationRecord对象的oldValue获取到null)

attributes

设为 true 以观察受监视元素的属性值变更。默认值为 false

characterData

设为 true 以监视指定目标节点或子节点树中节点所包含的字符数据的变化。无默认值。

characterDataOldValue

设为 true 以在文本在受监视节点上发生更改时记录节点文本的先前值。

childList

设为 true 以监视目标节点(如果 subtree 为 true,则包含子孙节点)添加或删除新的子节点。默认值为 false

subtree

设为 true 以将监视范围扩展至目标节点整个节点树中的所有节点。MutationObserverInit 的其他值也会作用于此子树下的所有节点,而不仅仅只作用于目标节点。默认值为 false

5. disconnect

在需要观测的逻辑已经执行完毕,不需要继续观测了,可以通过disconnect来阻止观察者继续接收消息

observer.disconnect()

直到再次调用其observe方法之前,该观察者对象包含的回调函数都不会再被调用。

6. 完整的使用案例

HTML部分

<div id="container" class="test-a"></div>
<button id="btn">Click</button>
<button id="disconnect">Disconnect</button>

JS部分

// 选择需要观察变动的节点
const targetNode = document.getElementById('container');

// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver((mutationsList, observer) => {
    for (let mutation of mutationsList) {
       if (mutation.type === 'childList') {
         const { addedNodes, removedNodes } = mutation;
         if (addedNodes.length) {
           // 增加了子节点
           console.log(`A ${addedNodes[0].nodeName} with text '${addedNodes[0].textContent}' has been added`);
         }
         if (removedNodes.length) {
           // 移除了子节点
           console.log(`A ${removedNodes[0].nodeName} has been removed`);
         }
       } else if (mutation.type === 'attributes') {
         console.log('The ' + mutation.attributeName + ' attribute was modified.');
       }
    }
});

// 以上述配置开始观察目标节点,并传入观察的配置(需要观察哪些变更)
observer.observe(targetNode, {
  attributes: true,
  childList: true,
  subtree: true,
});

let num = 1;
let p = null;
const btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
    if (num === 1) {
      targetNode.setAttribute('test-val', '1');
    }
    if (num === 2) {
      targetNode.classList.add('test-b');
    }
    if (num === 3) {
      p = document.createElement('p');
      p.innerText = 'OK!';
      targetNode.appendChild(p);
    }
    if (num === 4) {
      targetNode.removeChild(p);
    }
    num++;
})

const disconnectBtn = document.querySelector('#disconnect');
// 当完成所需的操作不需要继续观察了,调用disconnect方法取消观察
disconnectBtn.addEventListener('click', () => { observer.disconnect(); });

运行该页面后,在页面上的按钮点击3次,就可以看到控制台上输出了这样的4条日志

test-123.png

前面2条日志分别是修改了被观察节点的属性以及class触发的,第3条日志是往被观察元素中增加了DOM节点触发的,第4条日志是从元素中移除节点触发的

注意我们通过callback中接收的第1个参数mutationList可以获得变动的各种信息,包括变动的类型(mutation.type)、变动的属性名(mutation.attributeName)、塞入的节点(mutation.addedNodes)等等

参考

MDN MutationObserver