JavaScript MutationObserver API 详解:DOM 变化监听实战

作者:小圆点

时间:2026-10-24

浏览:439

本文详细介绍 JavaScript 中 MutationObserver API 的使用。涵盖实例化、observe 方法配置监听范围(属性、文本、子节点、子树)、MutationRecord 记录解析、异步回调机制、disconnect 终止监听及 takeRecords 获取记录等核心功能,并提供多元素监听与自定义属性过滤等实战示例。

文章目录

一、基本使用

MutationObserver 构造函数用于创建观察者实例,其接收一个回调函数作为参数。


let observer = new MutationObserver(() => console.log("change"));

console.log(observer);

该对象的原型链结构如下:

一个典型的 MutationObserver 实例包含以下关键方法:

一、基本使用

具体包括 disconnect、observer 以及 takeRecords 等方法。

1. 使用 observer 方法进行监听

observer 方法用于将 DOM 元素与观察者关联,并依据指定的配置选项执行监听任务。

其标准语法结构如下:


// 接收两个参数

observer(DOM元素, MutationObserverInit对象);

参数说明如下:

  • 第一个参数指定要监听的 DOM 元素,例如 document.body 或特定的 div 元素。
  • 第二个参数为配置对象,定义监听的范围,涵盖属性、文本内容或子节点变动等,以键值对形式传递。

示例 1:监听 body 元素 class 属性的变化:


let observer = new MutationObserver(() => console.log("change"));

// 监听body元素的属性变化

observer.observe(document.body, {

    attributes: true

});

// 更改body元素的class,会异步执行创建MutationObserver对象时传入的回调函数

document.body.className = "main";

console.log("修改了body属性");

// 控制台输出:

//    修改了body属性

//    change

上述代码中 change 的输出出现在修改 body 属性之后,这表明注册的回调函数是异步执行的,会在当前同步代码执行完毕后运行。

2. 回调函数接收 MutationRecord 实例数组

前文示例中回调函数仅输出字符串,无法直观反映具体的 DOM 变化细节。

实际上,回调函数接收一个 MutationRecord 实例数组,通过遍历该数组可获取详细的变更日志。


let observer = new MutationObserver(

    // 回调函数是一个 MutationRecord 实例数组。格式如下:

    //     [MutationRecord, MutationRecord, MutationRecord, ...]

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

document.body.className = "main";

console.log("修改了body属性");

// 控制台输出:

//    修改了body属性

//     (1) [MutationRecord]

MutationRecord 对象包含以下关键信息:

一个典型的 MutationRecord 实例结构如下:

一、基本使用

其中几个关键字段含义如下:

  • attributeName 表示发生变化的属性名称。
  • target 指向被修改的目标 DOM 节点。
  • type 表示变动的类型(如 attributes、characterData 等)。

若对 body 元素的属性进行多次修改,回调函数将收到包含多条记录的数组:


// MutationRecord

let observer = new MutationObserver(

    // 回调函数接收一个 MutationRecord 实例,是一个数组。

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

 

// 修改三次

document.body.className = "main";

document.body.className = "container";

document.body.className = "box";

 

// 控制台打印如下:

//     (3) [MutationRecord, MutationRecord, MutationRecord]

需要注意:

并非每次修改都立即触发一次回调,而是每次修改都会向 mutationRecords 数组追加一个 MutationRecord 实例,待所有同步代码执行完毕后,统一执行一次回调并打印结果。

若采用每次修改立即执行一次回调的机制,将会导致严重的性能问题。

3. 使用 disconnect 方法终止监听

若需停止监听,可调用 disconnect 方法。


let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

// 第一次修改

document.body.className = "main";

 

// 终止

observer.disconnect();

 

// 第二次修改

document.body.className = "container";

 

// 没有日志输出

此时控制台无日志输出,包括首次修改也无反应,因为回调执行具有异步特性。随后调用 observer 终止监听,后续修改将不再触发回调。

可通过 setTimeout 控制终止时机,确保回调能正常执行。


let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

// 第一次修改

document.body.className = "main";

 

// 终止

setTimeout(() => {

    observer.disconnect();

    // 第三次修改,下面修改不会回调了

    document.body.className = "container";

}, 0);

 

// 第二次修改

document.body.className = "container";

 

// 页面输出:

//    (2) [MutationRecord, MutationRecord]

终止后重新启用监听

监听终止后,可再次启动监听器,具体示例如下:


let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

// 第一次修改,会入 mutationRecords 数组

document.body.className = "main";

// 终止

setTimeout(() => {

    observer.disconnect();

    // 第二次修改,因为终止了,下面修改不会入 mutationRecords 数组

    document.body.className = "container";

}, 0);

setTimeout(() => {

    // 再次启用

    observer.observe(document.body, {

        attributes: true

    });

    // 修改body属性,会入 mutationRecords 数组

    document.body.className = "container";

}, 0);

 

// 控制台输出:

//    [MutationRecord]

//    [MutationRecord]

此例中回调函数执行了两次,打印了两条记录,具体分析如下:

  • 第一次输出对应首次修改,因后续无同步代码,故直接执行回调。
  • 第二次输出对应第三次修改,由于监听器已重新启用,因此正常执行回调。

第二次修改时,因 observer 已被终止,故修改 body 属性不会将记录加入 mutationRecords 数组。

4. 使用 takeRecords 方法获取修改记录

若需在终止 observer 之前处理已有的 mutationRecords 记录,可使用 takeRecords 方法主动获取。


let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

 

// 第一次修改,会入 mutationRecords 数组

document.body.className = "main";

// 第二次修改,会入 mutationRecords 数组

document.body.className = "container";

// 第三次修改,会入 mutationRecords 数组

document.body.className = "box";

// 取到修改记录,可以对其进行处理

let mutationRecords =  observer.takeRecords();

console.log(mutationRecords);

// 控制台打印:

//     (3) [MutationRecord, MutationRecord, MutationRecord]

console.log(observer.takeRecords());

// 控制台打印:

//    []

// 终止

observer.disconnect();

二、监听多个元素

前述示例仅监听单个元素,若需监听多个元素,可复用同一个 MutationObserver 实例。


let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

// 创建 div1 元素,并监听

let div1 = document.createElement("div");

observer.observe(div1, {

    attributes: true

});

div1.id = "box1";

 

// 创建div2并监听

let div2 = document.createElement("div");

observer.observe(div2, {

    attributes: true

});

div2.id = "box2";

 

// 控制台打印:

//    (2) [MutationRecord, MutationRecord]

控制台打印了两个 MutationRecord 对象,具体解析如下:

  • 第一个 MutationRecord 记录了 div1 的 id 属性变化。
  • 第二个 MutationRecord 记录了 div2 的 id 属性变化。

其他使用方式与单元素监听类似。

三、监听范围配置:MutationObserverInit 对象

前述示例主要监听属性,实际上还可监听文本内容、子节点变动等其他类型。

1. 观察属性变动

前例观察的是元素固有属性,此处补充自定义属性的监听示例。


let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

// 修改自定义的属性

document.body.setAttribute("data-id", 1);

 

// 控制台打印:

//    [MutationRecord]

修改自定义属性同样会将记录加入 mutationRecords 数组。

此外,data-id 属性常用于存储元素数据,若其值发生变化,程序即可监听并处理相应逻辑。

使用 attributeFilter 进行过滤:

若需仅监听特定属性的变化,可通过 attributeFilter 配置进行过滤。


let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true,

    // 设置白名单

    attributeFilter: ["data-id"]

});

 

// 修改白名单 attributeFilter 内的属性,会入 mutationRecords

document.body.setAttribute("data-id", 1);

 

// 修改不在白名单 attributeFilter 内的属性,不会入 mutationRecords

document.body.setAttribute("class", "main");

 

// 控制台打印:

//    [MutationRecord]

使用 attributeOldValue 记录旧值:

若需记录属性修改前的旧值,可将 attributeOldValue 设置为 true。


let observer = new MutationObserver(

    // MutationRecord对象中oldValue表示旧值

    (mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue))

);

observer.observe(document.body, {

    attributes: true,

    attributeOldValue: true,

});

// 第一次修改,因为原来没有值,所以旧值 oldValue = null

document.body.setAttribute("class", "main");

// 第二次修改,因为前面有改了一次,所以旧值 oldValue = main

document.body.setAttribute("class", "container");

 

// 控制台打印:

//    (2) [null, 'main']

2. 观察文本变动

监听文本需将 characterData 设置为 true ,且仅能监听文本节点。

具体示例如下:


<!-- 一个性感的div -->

<div id="box">Hello</div>

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

    // 获取文本节点

    let textNode = document.getElementById("box").childNodes[0];

    observer.observe(textNode, {

        // 观察文本变化

        characterData: true

    });

    // 修改文本

    textNode.textContent = "Hi";

 

    // 控制台打印:

    //    [MutationRecord]

</script>

若直接监听 div 元素本身,则无法生效,必须针对其文本节点。


<!-- 一个性感的div -->

<div id="box">Hello</div>

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

    // 监听div不会生效

    let box = document.getElementById("box");

    observer.observe(box, {

        characterData: true

    });

    box.textContent = "Hi";

 

    // 控制台无输出

</script>

使用 characterDataOldValue 记录旧值:

若需记录文本修改前的旧值,可将 characterDataOldValue 设置为 true。


<!-- 一个性感的div -->

<div id="box">Hello</div>

 

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue))

    );

    // 获取文本节点

    let textNode = document.getElementById("box").childNodes[0];

    observer.observe(textNode, {

        // 观察文本变化

        characterData: true,

        // 保留旧数据

        characterDataOldValue: true,

    });

    // 修改文本两次

    textNode.textContent = "Hi";

    textNode.textContent = "Nice";

 

    // 控制台打印:

    //    (2) ['Hello', 'Hi']

</script>

因 div 内内容原为 Hello,先改为 Hi,再改为 Nice,故两次修改的旧值分别为 Hello 和 Hi。

3. 观察子节点变动

MutationObserver 实例同样可观察目标节点子节点的变动情况。


<!-- 一个性感的div -->

<div id="box">Hello</div>

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

    // 获取div

    let box = document.getElementById("box");

    observer.observe(box, {

        // 观察子节点变化

        childList: true,

    });

    // 添加元素

    let span = document.createElement("span")

    span.textContent = "world";

    box.appendChild(span);

 

    // 控制台打印:

    //    [MutationRecord]

</script>

在 MutationRecord 实例中,addedNodes 属性记录了新增的节点。

移除节点示例:


<!-- 一个性感的div -->

<div id="box">Hello</div>

 

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

 

    // 获取div

    let box = document.getElementById("box");

    observer.observe(box, {

        // 观察子节点变化

        childList: true,

    });

    // 移除第一个子节点,就是Hello文本节点

    box.removeChild(box.childNodes[0]);

 

    // 控制台打印:

    //    [MutationRecord]

</script>

在 MutationRecord 实例中,removedNodes 属性记录了被移除的节点。

移动节点示例:

对现有节点进行移动操作时,会生成两条 MutationRecord 记录,因为移动本质上是先删除后添加的过程。


<!-- 一个性感的div -->

<div id="box">Hello<span>world</span></div>

 

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

 

    // 获取div

    let box = document.getElementById("box");

    observer.observe(box, {

        // 观察子节点变化

        childList: true,

    });

    // 将span节点移动到Hello节点前面

    box.insertBefore(box.childNodes[1], box.childNodes[0]);

    // 移动节点,实际是先删除,后添加。

 

    // 控制台打印:

    //    (2) [MutationRecord, MutationRecord]

</script>

4. 观察子树变动

前述监听仅针对当前设置的目标节点(如 body),即仅观察 body 元素及其直接子节点。

若需观察 body 及其所有后代节点的变动,可将 subtree 属性设置为 true。


<!-- 一个性感的div -->

<div id="box">Hello<span>world</span></div>

 

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

 

    let box = document.getElementById("box");

    observer.observe(box, {

        attributes: true,

        // 观察子树的变化

        subtree: true

    });

    // span元素的id属性变化就可以观察到

    box.childNodes[1].id = "text";

    // 控制台打印:

    //    [MutationRecord]

</script>

当 subtree 设置为 true 后,不仅 div 元素本身,其内部的 span 元素变动也可被观察到。

总结:

  • 1. MutationObserver 实例可用于观察 DOM 对象的变化。
  • 2. MutationRecord 实例记录了每一次具体的 DOM 变动。
  • 3. 回调函数在所有脚本任务完成后执行,即采用异步方式。
  • 4. 支持的观察类型包括属性、文本、子节点及子树。

到此这篇关于 JavaScript 中 MutationObServer 监听 DOM 元素详情的文章就介绍到这了,更多相关 JavaScript 中 MutationObserver 监听 DOM 元素内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章