作为前端开发,你了解MutationObserver吗?

news2024/12/27 4:09:59

目录

前言

演变过程

基础概念

MutationObserver

observe(target, options)

attributes:是否监听标签属性变化

childList:是否监听子节点变化

characterData:是否监听文本节点内容的变化

attributeOldValue:是否记录属性变化前的值

characterDataOldValue:是否记录文本节点内容变化前的值

subtree:是否监听后代节点变化

attributeFilter:过滤属性名称

disconnect()

takeRecords()

MutationRecord[]

MutationRecord的属性

MutationObserver的应用场景

检测DOM变化并做出响应

动态样式变化

标签之间通信

缺点

首先是性能损耗

其次是操作冲突

最后是无法在IFrame中监听变化

总结


前言

MutationObserver在开发中或许不常使用,但是特殊情况下确实可以解决某些问题。它与addEventListener有些类似,当用户触发了某些事件操作时会调用对应的回调

前些天在需求迭代中使用到了MutationObserver,由于Antd早期版本的弹窗没有做响应功能,以及代码中的弹窗许多没有进行二次封装,导致无法得知弹窗何时出现及消失,于是我使用前端Hack的方式取个巧,监听元素变化解决了此类问题,这里做个知识点分享

那么MutationObserver究竟是什么?如何使用?其在开发中发挥着什么作用?使用该API会有什么隐患?请继续往下看

演变过程

在Mutation标准化之前,开发者对DOM变化的非官方监听方式是使用定时器(轮询)机制,通过setTimeout或者setInterval来进行宏任务创建,观察节点的变化;

此外有些场景也可以通过事件委托机制addEventListener来监听操作及变化

后来MutationEvent的出现增强了DOM监听的拓展性和局限性,使Mutation标准化,但是MutationEvent采用的是同步的方式,并且是实时触发回调,即每次变化都会触发监听回调函数,十分损耗性能

于是就有了现在的MutationObserver,MutationObserver与Promise一样属于微任务队列,它采用的是异步的监听方式,所有的操作会统一放在回调中,当有操作时在下一个微任务执行时会触发监听回调;或者可以理解为:一个节点同时进行多个操作时,其变化会被记录到一个异步队列中,最终一次性展示,这样做既不会影响页面加载,也保证了DOM变化的监听

基础概念

MutationObserver是JS的API,可以用于观察文档中的 DOM 树变化,并在这些变化发生时执行特定的回调函数。

介绍一下基本用法,MutationObserver类接收一个回调函数,在标签发生变化时触发,参数mutationsList是MutationRecord对象(后面会详细讲)的数组,参数observer是当前MutationObserver的实例对象;observer实例存在函数observe,传入两个参数第一个是待监听的标签,第二个是配置项主要声明监听哪些属性,如childList,attributes等

const elem = document.querySelector("#elem");
// 创建观察者实例
const observer = new MutationObserver((mutationsList, observer) => {
    // 监听回调
    console.log(mutationsList, observer);
});
observer.observe(elem, {
    //至少要传一个配置
    attributes: true,
});
// 元素发生改变
elem.hidden = true;

MutationObserver

MutationObserver类的实例中有以下函数

observe(target, options)

观察指定的目标元素。第二个参数传入一个配置对象,以指定要监听的事件类型和其他选项

配置可以传入以下选项:

attributes:是否监听标签属性变化

在介绍基本用法时我们就举例说明了attributes配置,当hidden属性发生变化时,会触发监听回调

childList:是否监听子节点变化

接着上面的示例代码,我们将observe的配置变更为childList: true,就可以监听子节点的变化

  <body>
    <div id="elem"></div>
    <div id="son"></div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const son = document.querySelector("#son");
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList, observer);
      });
      observer.observe(elem, {
        childList: true,
      });
      elem.textContent = "小黑";
      elem.appendChild(son);
      elem.removeChild(son);
    </script>
  </body>

characterData:是否监听文本节点内容的变化

值得注意的是文本节点是标签的子节点,所以首先我们要监听标签的子节点才会有变化,比如

const elem = document.querySelector("#elem");
const textElem = elem.firstChild; // 这里获取标签的文本节点
const observer = new MutationObserver((mutationsList, observer) => {
  console.log(mutationsList, observer);
});
observer.observe(textElem, {
  characterData: true,
});
textElem.textContent = "小黑";

attributeOldValue:是否记录属性变化前的值

attributeOldValue必须配合attribute使用,我们先监听标签的attribute变化。

  <body>
    <div id="elem" name="阿黄"></div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList, observer);
      });
      observer.observe(elem, {
        attributes: true,
      });
      elem.setAttribute("name", "小黑");
    </script>
  </body>

当我们监听attributes属性的时候,会发现oldValue是null

 

如果我们加上

observer.observe(elem, {
  attributes: true,
  attributeOldValue: true,
});

就会存储原先的属性值 

characterDataOldValue:是否记录文本节点内容变化前的值

与attributeOldValue类似,characterDataOldValue是用来记录储存原先的文本值的,我们将文本改成小黑,可以看到在回调中oldValue的值是之前的阿黄

const elem = document.querySelector("#elem");
const textElem = elem.firstChild;
const observer = new MutationObserver((mutationsList, observer) => {
    console.log(mutationsList, observer);
});
observer.observe(textElem, {
    characterData: true,
    characterDataOldValue: true,
});
textElem.textContent = "小黑";

subtree:是否监听后代节点变化

我们依旧以上面的代码为例,如果有两个div嵌套,并且想监听最底层的div变化,此时就可以添加属性subtree和待监听的属性,比如监听所有后代节点的属性变化

  <body>
    <div id="elem">
      <div>
        <div></div>
      </div>
    </div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const child = elem.firstElementChild.firstElementChild;
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList, observer);
      });
      observer.observe(elem, {
        subtree: true,
        attributes: true,
      });
      child.setAttribute("name", "阿黄");
    </script>
  </body>

attributeFilter:过滤属性名称

在配置了attributes用来监听属性变化的同时,可以使用attributeFilter配置项来过滤属性名称,attributeFilter通过传入字符串数组[ "class","name" ]来进行过滤,比如我只想监听class名的变化

  <body>
    <div id="elem"></div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList, observer);
      });
      observer.observe(elem, {
        attributes: true,
        attributeFilter: ["class"],
      });
      elem.setAttribute("name", "阿黄");
      elem.setAttribute("class", "elem");
      elem.hidden = true;
    </script>
  </body>

此时只会显示class被修改后的回调

disconnect()

当我们需要取消监听标签变化时可以使用实例化对象MutationObserver的disconnect()函数进行中断,由于Dom树变化是异步的,所以使用延时来触发取消监听

const elem = document.querySelector("#elem");
const observer = new MutationObserver((mutationsList, observer) => {
    console.log(mutationsList, observer);
});
observer.observe(elem, {
    attributes: true,
});
elem.setAttribute("name", "阿黄");
setTimeout(() => {
    observer.disconnect();
    elem.hidden = true;
});

takeRecords()

在回调函数中第一个参数是mutationsList数组,此时我们如果想清空这个数组可以使用takeRecords函数达到重置的效果

const elem = document.querySelector("#elem");
const observer = new MutationObserver((mutationsList, observer) => {
    console.log(mutationsList, observer);
});
observer.observe(elem, {
    attributes: true,
});
elem.setAttribute("name", "阿黄");
elem.hidden = true;
observer.takeRecords();
elem.setAttribute("name", "小黑");

上述代码运行后只会打印name设置为小黑的操作

MutationRecord[]

在MutationObserver类实例化时传入一个观察者回调函数,其第一个参数是一个MutationRecord数组,接收的是发生变化的元素信息

MutationRecord的属性

target:发生变化的节点

type:变化的类型

  • attributes:属性被添加、修改或删除
  • characterData:标签的文本发生变化
  • childList:子节点被添加、修改顺序或删除

nextSibling:父节点的子节点后一位兄弟节点(insertBefore,removeChild)

previousSibling:父节点的子节点前一位兄弟节点(appendChild)

attributeName:当type是attributes时,表示发生变化的属性名称(setAttribute)

attributeNamespace:当type为attributes时,表示发生变化的属性命名空间名称(setAttributeNS)

addedNodes:被添加的节点

removedNodes:被删除的节点

oldValue:当配置了attributeOldValue或characterDataOldValue为true时记录的旧值

下面这段代码几乎涵盖了上述全部属性,可以参考一下

  <body>
    <div id="elem" name="阿黄">elem</div>
    <div id="son">son</div>
    <div id="prev">prev</div>
    <div id="next">next</div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const son = document.querySelector("#son");
      const prev = document.querySelector("#prev");
      const next = document.querySelector("#next");
      const elemText = elem.firstChild;
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList);
      });
      observer.observe(elem, {
        attributes: true,
        attributeOldValue: true,
        characterData: true,
        characterDataOldValue: true,
        subtree: true,
        childList: true,
      });
      // type: "characterData", oldValue: "elem"
      elemText.textContent = "阿黄";
      // oldValue: "阿黄", type: "attributes", attributeName :  "name"
      elem.setAttribute("name", "小黑");
      // attributeName: "name", attributeNamespace: "ns", type: "attributes"
      elem.setAttributeNS("ns", "NS:name", "阿黄");
      // type: "childList", removedNodes: NodeList[text], addedNodes: NodeList[text]
      elem.textContent = "小黑";
      // addedNodes:NodeList[div#prev], type: "childList"
      elem.appendChild(prev);
      // addedNodes:NodeList[div#next], type: "childList", previousSibling: div#prev
      elem.appendChild(next);
      // addedNodes: NodeList[div#son], type: "childList", previousSibling: div#prev, nextSibling: div#next
      elem.insertBefore(son, next);
      // removedNodes: NodeList[div#son], type: "childList", previousSibling: div#prev, nextSibling: div#next
      elem.removeChild(son);
    </script>
  </body>

MutationObserver的应用场景

下面是一些常用的场景

检测DOM变化并做出响应

比如使用MutationObserver实现图片懒加载,监视img标签的visibilitychange事件,做出响应;或者当元素的偏移top在窗口内时做出加载图片操作

动态样式变化

监听style或者class的变化做出响应,比如我之前的应用:监听antd的模态窗变化,做出后续操作

标签之间通信

通过监听data-key属性的变化发送、接收消息

缺点

MutationObserver固然好用,但是其缺点也比较明显

首先是性能损耗

虽然在MutationEvent的基础上优化了许多,但是监听body的操作对性能影响还是非常大的,一切用户操作可能都会使函数频繁的回调。

解决方式是尽量对小范围的节点进行监听,或者限制监听类型

其次是操作冲突

由于回调函数非唯一性,如果两个观察者监听变化后的操作有依赖关系可能会造成错误或者冲突

解决方式可以采用锁的机制,当两个条件都满足才能进入函数或者线程

最后是无法在IFrame中监听变化

MutationObserver操作是基于当前DOM进行监听的,所以无法跨线程与窗口

可以使用postmessage进行通信操作,可以参考之前关于窗口与线程通信的一篇文章

总结

本篇文章介绍了MutationObserver类的基本概念及使用,监听DOM的方式由最早的定时器、事件委托到MutationEvent最后到本文介绍的MutationObserver;它采用的是异步非实时的监听方式,监听回调返回一个MutationRecord列表,记录Dom的操作变化;此外,我们可以通过实例的observe对某个节点进行监听,监听的类型主要有attributes(属性),childList(子节点变化),characterData(文本节点变化),其他配置项还有attributeOldValue(记录属性旧值),characterDataOldValue(记录文本旧值),subtree(监听后代节点),attributeFilter(属性名过滤);最后介绍了MutationObserver的应用场景及缺点,应用场景主要就是监听DOM变化采取对应操作,缺点主要是:性能损耗,操作冲突,线程限制;

以上就是文章全部内容,希望对你有帮助,如果觉得文章不错,还请三连支持一下作者,非常感谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/636447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端058_权限系统(身份认证+退出+刷新令牌)_刷新令牌获取新的认证信息

当访问令牌 access_toke 过期,后台会响应状态码 401 ,通过刷新令牌 refresh_toke 获取新令牌。获取后重新发送引发获取新令牌的请求。 1、请求拦截401错误实现刷新令牌请求 所有的请求后台数据,都是通过在 src/utils/request.js 封装的 axios 对象进行发送请求,所以当调…

蓝牙标签协议

1.Request write block size command CMD DATA 0x01 无 Response for write block size command CMD DATA (short) 0x01 Block size 注意&#xff1a;short是低位在前&#xff0c;高位在后 2.Request write screen command CMD DATA 0x02 Image length(int) Imag…

GIS软件中网络分析的5种应用

什么是网络分析&#xff1f;几乎每个人都需要一个网络分析的类型在他们的生活中。 例如&#xff0c;去海滩的最短路线是什么&#xff1f;应该在哪里建一所医院来最好地服务一个社区&#xff1f;如何优化运输车队&#xff1f; 以下是5种最常见的网络分析类型&#xff1a;点对点…

原来Allegro工程师还分这几个等级,薪资差距好大

随着电子设计领域的不断发展&#xff0c;Allegro工程师的需求也越来越高&#xff0c;在发展过程中&#xff0c;不同等级的Allegro工程师之间存在着薪资和工作范围上的差异&#xff0c;那么你知道不同等级的Allegro工程师有什么不同吗&#xff1f; NO.1初级Allegro工程师 ①分布…

HYA-D-M5、HYA-C-01、HYA-D-02气控电磁阀

HY-C-M5、HY-D-01、HY-D-02、HYA-D-M5、HYA-C-01、HYA-D-02、H280-4-D-S-L、H280-5-D-D-L、H380-5-G-S-L、H380-5-D-S-L电磁阀分电控及气控。维护须知&#xff1a; 1&#xff0c;安装时&#xff0c;请注意气体流动方向及接管是否正确&#xff0c;电压是否符合要求2.请注意防尘…

内部知识库搭建的意义何在?可以给哪些内部人员使用?

随着企业的不断发展和壮大&#xff0c;内部知识管理成为了越来越重要的一项工作。而内部知识库搭建则是内部知识管理的重要手段之一。本文将介绍内部知识库搭建的意义以及可供哪些内部人员使用&#xff0c;以及如何搭建一个高效的内部知识库。 内部知识库搭建的意义&#xff1…

拓展冒泡排序

冒泡排序 冒泡排序的动态演示 比较相邻的元素。 1&#xff0c;如果第一个比第二个大&#xff08;升序&#xff09;&#xff0c;就交换他们两个。 2&#xff0c;对每一对相邻元素作同样的工作&#xff0c;从开始第一对到结尾的最后一对。这步做完后&#xff0c; 最后的元素会是…

解决Antd Tree组件,二次点击时不取消选中,保持高亮

一、问题概述 ant design 提供的 Tree树组件 支持点击高亮树节点&#xff0c;再次点击取消高亮。 默认效果如下&#xff1a; 然而大多数业务场景下&#xff0c;我们希望多次点击同一个节点不会取消他的选中效果。 二、解决方案 监听onSelect时间&#xff0c;并使用select…

PG系列1:windows下安装PG15

文章目录 一. 下载PG二. 开始安装PG2.1 开始安装2.2 验证 一. 下载PG 官网地址: https://www.postgresql.org/选择Download 选择windows Download the installer 点击下载 二. 开始安装PG 2.1 开始安装 这个安装很简单&#xff0c;直接下一步即可&#xff0c;此处…

5.Java内存模型之JMM

Java内存模型之JMM 5.1 先从大场面试开始 你知道什么是Java内存模型JMM吗&#xff1f; JMM和volatile他们两个之间的关系&#xff1f; JMM没有那些特征或者它的三大特征是什么&#xff1f; 为什么要有JMM&#xff0c;它为什么出现&#xff1f;作用和功能是什么&#xff1f; hap…

智能井盖:智慧城市下的井盖管理新模式

随着全球城市化进程的加快&#xff0c;智慧城市的概念越来越受到关注。井盖作为城市的基础设施之一&#xff0c;井盖的安全管理成为城市管理者关注的重要问题。传统的井盖管理方式面临诸多挑战&#xff0c;如人力成本高、巡检效率低、隐患难以发现等。随着智慧城市的发展&#…

leangoo领歌敏捷工具中,如何快速查看项目内所有任务卡片

项目管理员能不能快捷的查看整个项目内的所有任务&#xff1f; 能不能快捷查看项目内某一个成员的所有任务&#xff1f; 能不能快捷的在项目内通过一些条件选择查看任务&#xff1f; 可以导出项目内某一个人的所有任务吗&#xff1f;方便做一些统计 等等... 这些现在Leang…

路径规划 | 图解Informed RRT*算法(附ROS C++/Python/Matlab仿真)

目录 0 专栏介绍1 Informed RRT*原理2 Informed RRT*流程3 ROS C实现4 Python实现5 Matlab实现 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细介绍全局规划(图搜索、采样法、智能算法等)&#xff1b;局部…

超全、超详细的Redis学习笔记总结

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

「2024」预备研究生mem-消序核心原则

一、消序 二、核心原则 相同备选池 三、练习题

数据库索引简介及优化

索引 1.索引简介 1.1 概念 MySQL官方对索引的定义为&#xff1a;索引&#xff08;Index&#xff09;是帮助MySQL高效获取数据的数据结构。索引的本质&#xff1a;索引是数据结构。 注&#xff1a;在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&…

OOM 原因及解决方案

1. 什么是OOM 1.1 OOM 含义: OOM, 全称 “Out Of Memory”, 意思是 “内存用完了”。 它来源于 java.lang.OutOfMemoryError。 1.2 为什么会出现OOM: 官方介绍为当 JVM 因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时, 就会抛出 java.lang.OutOfMemo…

Windows命令行压缩gz文件

tar -help win10好像是某个版本号之后&#xff0c;才能使用tar命令&#xff0c;所以想要直接在win10上面使用tar命令&#xff0c;就更新系统吧。 按 winr 键后输入 cmd 打开命令行终端&#xff0c;输入 tar -help 命令&#xff0c;如下所示。 tar -cf xxx.tar.gz ./xxx //是…

【电路】电路与电子技术基础 课堂笔记 第11章 数制、编码与逻辑代数

11.1 数制与数制转换 11.2 二进制数的编码 1. 二-十进制&#xff08;BCD&#xff09;码 把十进制数的每一位用多位二进制数表示&#xff0c;称为二进制编码的十进制数&#xff0c;简称BCD编码。 具有二进制数的形式&#xff0c;又具有十进制数的特点。 2. 8421码 3. 2421码…

老板谈上4休3工作制1个月后效果:不建议新公司模仿

大家好&#xff01;我是老洪&#xff01; 刚看到一则关于上4休3工作制的资讯&#xff0c;聊两句。 媒体是这样报道的。 一位长沙公司的老板在实施上4休3工作制一个月后表示&#xff0c;不建议新公司模仿。 他指出&#xff0c;个别员工的自律问题在上四休三后暴露出来&#xff0…