JS DOM编程笔记 - MutationObserver是干什么的(二三)

636 阅读3分钟

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

今天我们来学习如何使用 JavaScript的 MutationObserver API 来监听对 DOM 树所做的更改。

MutationObserver 简介

MutationObserver API 允许我们监视对 DOM 树所做的更改。当 DOM 节点发生变化时,我们可以调用回调函数对变化做出反应。

使用 MutationObserver API 的基本步骤是:

首先,定义DOM变化时将执行的回调函数:

function callback(mutations) {
  // 
}

其次,创建一个 MutationObserver 对象并将回调传给 MutationObserver() 构造函数:

let observer = new MutationObserver(callback);

然后,调用observe()方法开始观察DOM变化。

observer.observe(targetNode, observerOptions);

observe() 方法有两个参数。target是要监视更改的节点的根元素。 observerOptions 参数包含指定将哪些 DOM 更改报告给观察者的回调属性。

最后,通过调用 disconnect() 方法停止观察 DOM 变化:

observer.disconnect();

observerOptions 参数

observe() 方法的第二个参数让我们指定 MutationObserver 的参数:

let options = {
  childList: true,
  attributes: true,
  characterData: false,
  subtree: false,
  attributeFilter: ['attr1', 'attr2'],
  attributeOldValue: false,
  characterDataOldValue: false
};

我们不需要使用所有属性。但是,要使 MutationObserver 正常工作,至少需要将 childListattributescharacterData 之一设置为 true,否则 observer() 方法将会报错。

观察子元素变化

老规矩,还是假设有一个普普通通的列表:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>MutationObserver示例: 子元素</title>
</head>
<body>
  <ul id="language">
    <li>HTML</li>
    <li>CSS</li>
    <li>JavaScript</li>
    <li>TypeScript</li>
  </ul>

  <button id="btnStart">开始观察</button>
  <button id="btnStop">停止观察</button>
  <button id="btnAdd">添加元素</button>
  <button id="btnRemove">删除最后一个元素</button>
</body>
</html>

下面的例子说明了如何使用mutation options 对象的childList 属性来监控子节点的变化。

然后查找到所有按钮元素,停止观察按钮为禁用状态。

// 查找列表元素
let list = document.querySelector('#language');

// 查找按钮元素
let btnAdd = document.querySelector('#btnAdd');
let btnRemove = document.querySelector('#btnRemove');
let btnStart = document.querySelector('#btnStart');

let btnStop = document.querySelector('#btnStop');
btnStop.disabled = true;

其次,声明一个 log() 函数,该函数将用作 MutationObserver 的回调:

function log(mutations) {
  for (let mutation of mutations) {
    if (mutation.type === 'childList') {
      console.log(mutation);
    }
  }
}

第三,创建一个新的 MutationObserver 对象:

let observer = new MutationObserver(log);

第四,在options对象的childList设置为true的情况下,通过调用observe()方法,在开始观察按钮被点击时,开始观察列表元素子节点的DOM变化:

btnStart.addEventListener('click', function () {
  observer.observe(list, {
    childList: true
  });

  btnStart.disabled = true;
  btnStop.disabled = false;
});

五、点击添加元素按钮时添加一个新的li列表项:

let counter = 1;
btnAdd.addEventListener('click', function () {
  // 创建一个新的li元素
  let item = document.createElement('li');
  item.textContent = `子元素 ${counter++}`;

  // 追加到列表的末尾
  list.appendChild(item);
});

六、当点击删除最后一个元素按钮时,删除列表的最后一个子元素:

btnRemove.addEventListener('click', function () {
  list.lastElementChild ?
    list.removeChild(list.lastElementChild) :
  	console.log('没有子元素了');
});

最后,通过调用 MutationObserver 对象的 disconnect() 方法,在点击停止观察按钮时停止观察对 DOM 更改:

btnStop.addEventListener('click', function () {
  observer.disconnect();    
  // 改变按钮状态
  btnStart.disabled = false;
  btnStop.disabled = true;
});

最后贴上全部代码:

(function () {
  // 查找列表元素
  let list = document.querySelector('#language');

  // 查找按钮元素
  let btnAdd = document.querySelector('#btnAdd');
  let btnRemove = document.querySelector('#btnRemove');
  let btnStart = document.querySelector('#btnStart');

  let btnStop = document.querySelector('#btnStop');
  btnStop.disabled = true;
  
  function log(mutations) {
    for (let mutation of mutations) {
      if (mutation.type === 'childList') {
        console.log(mutation);
      }
    }
  }

  let observer = new MutationObserver(log);

  btnStart.addEventListener('click', function () {
    observer.observe(list, {
      childList: true
    });

    btnStart.disabled = true;
    btnStop.disabled = false;
  });

  btnStop.addEventListener('click', function () {
    observer.disconnect();    
    // 改变按钮状态
    btnStart.disabled = false;
    btnStop.disabled = true;
  });
  
  let counter = 1;
  btnAdd.addEventListener('click', function () {
    // 创建一个新的li元素
    let item = document.createElement('li');
    item.textContent = `子元素 ${counter++}`;

    // 追加到列表的末尾
    list.appendChild(item);
  });
  
  btnRemove.addEventListener('click', function () {
    list.lastElementChild ?
      list.removeChild(list.lastElementChild) :
    	console.log('没有子元素了');
  });

})();

我们将所有代码放在一个 IIFE(立即调用函数表达式)中。防止对全局变量的污染。

点击此链接查看在线示例:codepen.io/cmdfas-the-…

当我们点击添加元素按钮可以看到浏览器控制台中的打印:

image-20211108224128488

观察属性的变化

要观察属性的更改,请使用下面的options对象属性:

let options = {
  attributes: true
}

如果想要观察一个或多个指定属性的变化而忽略其他属性,可以使用 attributeFilter 属性:

let options = {
  attributes: true,
  attributeFilter: ['class', 'style']
}

在此示例中,每次类或样式属性更改时,MutationObserver都会调用回调。

观察目标节点及子节点

要观察目标节点及其子节点DOM树,将options对象的subtree属性设置为 true:

let options = {
  subtree: true
}

观察文本内容变化

要观察节点的文本内容更改,将options对象的characterData属性设置为 true:

let options = {
  characterData: true
}

访问旧值

要访问属性的旧值,将options对象的 attributeOldValue 属性设置为 true:

let options = {
  attributes: true,
  attributeOldValue: true
}

同样,可以通过将 options 对象的 characterDataOldValue 属性设置为 true 来访问文本内容的旧值:

let options = {
  characterData: true,
  subtree: true,
  characterDataOldValue: true
}

最后

MutationObserver API平时使用的可能比较少,你用到过吗?

一起学习更多前端知识,微信搜索【小帅的编程笔记】,每天更新