当前位置: 首页 > news >正文

DOM 规范中的 MutationObserver 接口

MutationObserver 接口

DOM规范中的 MutationObserver 接口可以在DOM被修改时异步执行回调。

使用MutationObserver可以观察整个文档、DOM树的一部分或某个元素,元素属性、字节点、文本等。

新引进的MutationObserver接口取代了已废弃的MutationEvent。

MutationObserver 实例

MutationObserver实例通过调用MutationObserver构造函数并传入一个回调函数创建。

新创建的MutationObserver实例,需要使用observe()方法与DOM进行关联。

observe()

两个必填参数:

要观察的DOM节点

MutationObserveInit 对象

MutationObserveInit对象用于控制观察哪些方面的变化,是一个键值对形式配置选项的字典。

回调与 MutationRecord

MutationObserver(构造函数)中的参数(回调函数),每个回调都会收到一个MutationRecord实例的数组(第一个参数),记录发生了什么变化以及DOM的哪部分收到了影响。

因为回调执行之前可能同时发生多个满足观察条件的事件,所以每次执行回调都会传入一个包含按顺序入队的MutationRecord实例的数组。

new MutationObserver(fn)

参数:回调函数fn。

回调函数fn的参数:

第一个参数:MutationRecord实例的数组

第二个参数:观察变化的MutationObserver的实例

MutationObserver 的回调函数可以多次执行。每当观察到 DOM 树的变化时,回调函数就会被调用。这些变化包括但不限于节点的添加、删除、属性的更改等。

那么问题来了

本例中,为什么回调中的 console.log 只输出了一次?

let ob = new MutationObserver(function () {console.log("%cbody attributes changed", "color: red; font-weight:bold;font-size: 20px;");
});
ob.observe(document.body, { attributes: true });
document.body.setAttribute("foo", "bar");
document.body.className = "newClass";

解答(来自通义灵码)

1.MutationObserver 是异步执行的

  • MutationObserver 的回调是通过 微任务队列(microtask queue) 异步触发的。
  • 当你在短时间内连续修改 DOM 属性时,浏览器会将这些变更合并为一个批次,并只调用一次回调函数

2. 属性变更被合并处理

  • 在你的代码中:

    document.body.setAttribute("foo", "bar"); document.body.className = "newClass";

    这两个操作都是对 document.body 的属性进行修改。
  • 因为它们在同一个事件循环中发生,所以 MutationObserver 只会收到一次“属性变化”的通知,并触发一次回调。

🧪 实验验证

如果你希望看到两次日志输出,可以使用 setTimeout 将这两个修改分开到不同的事件循环中:

document.body.setAttribute("foo", "bar"); setTimeout(() => { document.body.className = "newClass"; }, 0);

这样,MutationObserver 会在两个独立的 microtask 中检测到两次属性变更,从而触发两次回调。


✅ 总结

现象原因
console.log 只输出一次浏览器将多个 DOM 修改合并为一次变更通知,回调只执行一次
关键点说明
异步机制MutationObserver 使用微任务队列处理回调
批量优化浏览器自动合并多次变更以提升性能
控制输出次数可通过 setTimeout 或其他方式分隔变更事件

🛠️ 建议改进代码

// 分开两次 DOM 操作,确保触发两次回调 document.body.setAttribute("foo", "bar"); setTimeout(() => { document.body.className = "newClass"; }, 0);

这样就能在控制台看到两次 "body attributes changed" 的日志输出。

再记录一个通用示例

部分代码来自夸克浏览器。

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>木木的学习笔记</title><style>#target {border: 1px solid black;padding: 10px;margin-top: 10px;}</style></head><body><div class="container"><div class="navBox"></div><p>MutationObserver 的回调函数可以多次执行。每当观察到 DOM 树的变化时,回调函数就会被调用。这些变化包括但不限于节点的添加、删除、属性的更改等。</p><p>测试</p><div id="target">这是一个目标元素。</div><br /><button onclick="changeText()">改变文本</button></div><script src="./js/nav.js"></script><script>//加载页面导航var navObj = {title: "MutationObserver",chapter: "",section: "",part: "",};setNav(navObj);</script><script>// 选择需要观察变动的节点const targetNode = document.getElementById("target");// 配置观察选项const config = { childList: true, subtree: true, attributes: true, characterData: true };// 创建一个回调函数,当观察到DOM变动时会被调用const callback = function (mutationsList, observer) {console.log("DOM changed");for (let mutation of mutationsList) {if (mutation.type === "childList") {console.log("A child node has been added or removed.");} else if (mutation.type === "attributes") {console.log(`The ${mutation.attributeName} attribute was modified.`);} else if (mutation.type === "characterData") {console.log("Character data in the node has been changed.");}}};// 创建一个观察器实例并传入回调函数const observer = new MutationObserver(callback);// 开始观察目标节点observer.observe(targetNode, config);// 函数用于改变目标元素的文本内容let count = 0;function changeText() {targetNode.textContent = `文本已被第 ${++count} 次改变!`;}</script></body>
</html>

这个示例中,每次修改都被记录。

 

http://www.dtcms.com/a/282621.html

相关文章:

  • 【LeetCode 热题 100】543. 二叉树的直径——DFS
  • STM32 写选项字 关键要加载HAL_FLASH_OB_Launch
  • 红黑树、B树、B+树
  • 轻松将文件从 iPhone 传输到 Mac
  • 中医文化学习软件,传承国粹精华
  • Redis Key 设计原则
  • Excel转PDF的三种方法
  • 基于大数据的网络文学推荐分析系统的设计与实现【海量书籍、自动爬虫】
  • 《通信原理》学习笔记——第二章
  • AI进化论15:通用人工智能(AGI)的“终极梦想”与“潘多拉魔盒”
  • AI炒作,AGI或在2080年之前也无法实现,通用人工智能AGI面临幻灭
  • 基于 .Net Core+MySQL开发(WinForm)翻译平台
  • rLLM:用于LLM Agent RL后训练的创新框架
  • 前端面试题(React 与 Vue)
  • #Datawhale组队学习#7月-强化学习Task1
  • Java大厂面试实录:从电商场景到AI应用的深度技术考察
  • 如何通过域名白名单​OVP防盗链加密视频?
  • 【游戏引擎之路】登神长阶(十八):3天制作Galgame引擎《Galplayer》——无敌之道心
  • AJ Security:实用的 Java Web 安全库
  • Qt获取hid设备信息
  • 抖音小程序开发如何避坑?如何接口调用与性能优化?
  • Nacos 开源 MCP Router,加速 MCP 私有化部署
  • 运维打铁: Ruby 脚本在运维自动化中的应用探索
  • leetcode 3201. 找出有效子序列的最大长度 I 中等
  • K8s 自定义调度器 Part1:通过 Scheduler Extender 实现自定义调度逻辑
  • AI Agent开发学习系列 - langchain之LCEL(1):LangChain LCEL链式编排与RAG增强实践
  • FunASR Paraformer-zh:高效中文端到端语音识别方案全解
  • Django ORM 查询工具对象详解
  • Linux 命令:uname
  • Scalefusion 与 EasyControl 对比:轻量级方案与全功能 IoT MDM 的深度碰撞