JavaScript 变异观察器

摘要:在本教程中,您将学习如何使用 JavaScript MutationObserver API 监控对 DOM 树所做的更改。

JavaScript 变异观察器 API 简介

MutationObserver API 允许您监控对 DOM 树所做的更改。当 DOM 节点发生变化时,您可以调用一个回调函数 来对这些变化做出反应。

使用 MutationObserver API 的基本步骤是

首先,定义一个在 DOM 发生变化时执行的回调函数

function callback(mutations) {
    // 
}Code language: JavaScript (javascript)

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

let observer = new MutationObserver(callback);Code language: JavaScript (javascript)

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

observer.observe(targetNode, observerOptions);Code language: JavaScript (javascript)

observe() 方法有两个参数。target 是要监控更改的节点子树的根。observerOptions 参数包含指定应向观察者的回调报告哪些 DOM 更改的属性。

最后,通过调用 disconnect() 方法停止观察 DOM 更改

observer.disconnect();Code language: JavaScript (javascript)

变异观察器选项

observe() 方法的第二个参数允许您指定选项来描述 MutationObserver

let options = {
    childList: true,
    attributes: true,
    characterData: false,
    subtree: false,
    attributeFilter: ['attr1', 'attr2'],
    attributeOldValue: false,
    characterDataOldValue: false
};Code language: JavaScript (javascript)

您不需要使用所有选项。但是,为了使 MutationObserver 工作,至少需要将 childListattributescharacterData 中的一个设置为 true,否则 observer() 方法将抛出错误。

观察对子元素的更改

假设您有以下列表

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MutationObserver Demo: ChildList</title>
</head>
<body>
    <ul id="language">
        <li>HTML</li>
        <li>CSS</li>
        <li>JavaScript</li>
        <li>TypeScript</li>
    </ul>

    <button id="btnStart">Start Observing</button>
    <button id="btnStop">Stop Observing</button>
    <button id="btnAdd">Add</button>
    <button id="btnRemove">Remove the Last Child</button>

    <script src="app.js"></script>
</body>
</html>
Code language: HTML, XML (xml)

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

首先,使用 querySelector() 方法选择元素(如 listbuttons)。默认情况下,停止观察 按钮处于 disabled 状态。

// selecting list
let list = document.querySelector('#language');

// selecting buttons
let btnAdd = document.querySelector('#btnAdd');
let btnRemove = document.querySelector('#btnRemove');
let btnStart = document.querySelector('#btnStart');

let btnStop = document.querySelector('#btnStop');
btnStop.disabled = true;Code language: JavaScript (javascript)

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

function log(mutations) {
    for (let mutation of mutations) {
        if (mutation.type === 'childList') {
            console.log(mutation);
        }
    }
}Code language: JavaScript (javascript)

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

let observer = new MutationObserver(log);
Code language: JavaScript (javascript)

第四,当单击 开始观察 按钮时,开始观察列表元素子节点的 DOM 变化,方法是调用 observe() 方法,并将 options 对象的 childList 设置为 true

btnStart.addEventListener('click', function () {
    observer.observe(list, {
        childList: true
    });
    
    btnStart.disabled = true;
    btnStop.disabled = false;
});
Code language: JavaScript (javascript)

第五,当单击 添加 按钮时,添加一个新的列表项

let counter = 1;
btnAdd.addEventListener('click', function () {
    // create a new item element
    let item = document.createElement('li');
    item.textContent = `Item ${counter++}`;

    // append it to the child nodes of list
    list.appendChild(item);
});
Code language: JavaScript (javascript)

第六,当单击 移除 按钮时,移除 list 的最后一个子节点

btnRemove.addEventListener('click', function () {
    list.lastElementChild ?
        list.removeChild(list.lastElementChild) :
        console.log('No more child node to remove');
});
Code language: JavaScript (javascript)

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

btnStop.addEventListener('click', function () {
    observer.disconnect();    
    // set button states
    btnStart.disabled = false;
    btnStop.disabled = true;
});Code language: JavaScript (javascript)

将所有内容放在一起

(function () {
    // selecting the list
    let list = document.querySelector('#language');

    // selecting the buttons
    let btnAdd = document.querySelector('#btnAdd');
    let btnRemove = document.querySelector('#btnRemove');
    let btnStart = document.querySelector('#btnStart');

    // disable the stop button
    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();

        // Set the button state
        btnStart.disabled = false;
        btnStop.disabled = true;
    });

    let counter = 1;
    btnAdd.addEventListener('click', function () {
        // create a new item element
        let item = document.createElement('li');
        item.textContent = `Item ${counter++}`;

        // append it to the child nodes of list
        list.appendChild(item);
    });

    btnRemove.addEventListener('click', function () {
        list.lastElementChild ?
            list.removeChild(list.lastElementChild) :
            console.log('No more child node to remove');
    });

})();
Code language: JavaScript (javascript)

请注意,我们将所有代码放在一个IIFE(立即调用函数表达式)中。

观察属性的变化

要观察属性的变化,您使用 options 对象的以下 attributes 属性

let options = {
  attributes: true
}
Code language: JavaScript (javascript)

如果您想观察对一个或多个特定 attributes 的更改,同时忽略其他更改,可以使用 attributeFilter 属性

let options = {
  attributes: true,
  attributeFilter: ['class', 'style']
}Code language: JavaScript (javascript)

在此示例中,MutationObserver 将在每次 classstyle 属性发生更改时调用回调。

观察对子树的更改

要监控目标节点及其节点子树,您将 options 对象的 subtree 属性设置为 true

let options = {
    subtree: true
}
Code language: JavaScript (javascript)

观察对字符数据的更改

要监控节点的文本内容的变化,您将 options 对象的 characterData 属性设置为 true

let options = {
    characterData: true
}Code language: JavaScript (javascript)

访问旧值

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

let options = {
    attributes: true,
    attributeOldValue: true
}Code language: JavaScript (javascript)

类似地,您可以通过将 options 对象的 characterDataOldValue 属性设置为 true 来访问字符数据的旧值

let options = {
    characterData: true,
    subtree: true,
    characterDataOldValue: true
}Code language: JavaScript (javascript)

变异观察器的实际示例

在 JavaScript 应用程序中,页面上的元素通常是动态生成的。要等待动态元素,您需要使用 MutationObserver

以下 waitForElement() 函数使用 MutationObserver 等待一个或多个由选择器指定的元素。

function waitForElement(selector) {
  return new Promise((resolve) => {
    if (document.querySelector(selector)) {
      return resolve(element);
    }
    const observer = new MutationObserver(() => {
      const element = document.querySelector(selector);
      if (element) {
        resolve(element);
        observer.disconnect();
      }
    });
    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
  });
}Code language: JavaScript (javascript)

它是如何工作的。

waitForElement() 函数返回一个 Promise。一旦元素可用,该 Promise 将被解析。

首先,如果元素可用,则解析元素

if (document.querySelector(selector)) {
    return resolve(element);
}Code language: JavaScript (javascript)

其次,如果元素不可用,则创建一个新的 MutationObserver 对象来观察 DOM 树

const observer = new MutationObserver(() => {
  const element = document.querySelector(selector);
  if (element) {
    resolve(element);
    observer.disconnect();
  }
});Code language: JavaScript (javascript)

观察者对象将在元素可用时调用 resolve() 函数,并停止观察 DOM 树。

第三,观察整个 DOM 树的元素

observer.observe(document.body, {
      childList: true,
      subtree: true,
});Code language: CSS (css)

因为 waitForElement() 返回一个 Promise,所以您可以使用 then() 方法,如下所示

waitForElement()('.a-class').then((element) => {
    console.log('Element is ready');
    console.log(element.textContent);
});Code language: JavaScript (javascript)

或者您可以使用 await 语法

const element = await waitForElement()('.a-class');
console.log(element.textContent);Code language: JavaScript (javascript)

在本教程中,您已经了解了 JavaScript MutationObserver API,它监控 DOM 更改,并在每次更改发生时执行一个回调。

本教程对您有帮助吗?