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

这篇具有很好参考价值的文章主要介绍了作为前端开发,你了解MutationObserver吗?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

演变过程

基础概念

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

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

如果我们加上

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

就会存储原先的属性值 

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

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 = "小黑";

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

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>

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

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被修改后的回调

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

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设置为小黑的操作

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

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变化采取对应操作,缺点主要是:性能损耗,操作冲突,线程限制;

以上就是文章全部内容,希望对你有帮助,如果觉得文章不错,还请三连支持一下作者,非常感谢!文章来源地址https://www.toymoban.com/news/detail-479991.html

到了这里,关于作为前端开发,你了解MutationObserver吗?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 前端开发Docker了解

    docker主要解决了最初软件开发环境配置的困难,完善了虚拟机部署的资源占用多,启动慢等缺点,保证了一致的运行环境,可以更轻松的维护和扩展。 docker在linux容器的基础上进行了进一步的封装,提供更简单易用的接口 。 把自己的应用放入容器,应用在docker的虚拟容器里

    2024年02月02日
    浏览(30)
  • 前端开发需要了解的工具集合

    前端开发需要了解的一些工具,这些工具能够帮助你在项目开发中事半功倍。 1. nrm:  npm  registry 管理器 registry :  npm  远程仓库的地址。 由于众所周知的原因, npm  官方仓库在国内特别的慢,所以我们需要用一些替代性方案,一种方案就是切换  npm  registry 到国内的镜像仓

    2024年02月13日
    浏览(31)
  • 《前端开发 实践之 构建工具的了解》

    前端构建工具之一:vite vue官方;打包工具;vue 项目本地构建部署工具 类似的前端项目打包工具还有:webpack等等 与其他打包工具区别可能有这几个方面:打包所消耗时间(打包速度) vue 脚手架:vue-cli 可以本地初始化项目,我们也可以使用 Vite 替代脚手架创建项目 基本使

    2024年02月14日
    浏览(39)
  • dockerfile不可以使用../作为路径在上级目录查找文件

    在 Dockerfile 中,不能直接使用 …/ 跳转到上级目录。Dockerfile 中的路径是相对于构建上下文路径的,而构建上下文指定了在构建镜像时可访问的文件和目录的范围。 如果你需要在 Dockerfile 中引用上级目录中的文件或目录,可以将上级目录作为构建上下文,并使用相对于构建上

    2024年01月25日
    浏览(39)
  • 前端jd要求:了解一门后端开发语言优先 解决方案之Node.js

    作为前端开发者,了解一门后端开发语言可以为我们提供更多的职业机会和技术优势。在当今的技术领域中,前后端分离的开发模式已经成为主流,前端和后端的协作和沟通变得越来越紧密。因此,作为前端开发者,学习一门后端语言已经成为提高自己技能的重要途径。 以下

    2024年02月12日
    浏览(51)
  • uniapp和springboot微信小程序开发实战:前端架构搭建之HBuilder X创建项目以及目录介绍

    HBuilder是DCloud(数字天堂)推出的一款支持HTML5的Web开

    2024年02月09日
    浏览(58)
  • 从技能需求到就业前景,了解前端和后端开发的优缺点和个人选择

    命运决定的不是你的人生,能决定你人生的只有自己。 前端和后端是Web开发中两个不可或缺的领域。前端开发主要负责网页的界面设计和交互行为,利用HTML、CSS和JavaScript等技术,将网页的外观和功能实现。而后端开发则主要负责网站的后台逻辑和数据处理部分,利用服务器

    2024年02月08日
    浏览(48)
  • 作为程序员,你很有必要了解一下IVX

    iVX 是一个“零代码”的可视化编程平台,拥有方便的在线集成开发环境,不需要下载开发环境,打开浏览器即可随时随地进行项目编辑。iVX 还拥有“一站式”的云资源,通过这一套一站式服务,iVX 可以实现一站式研发、一站式部署、一站式维护。iVX相当于“一款零代码可视

    2024年02月15日
    浏览(55)
  • Premint工具,作为普通人我们需要了解哪些内容?

    如今加密大环境冷静期,大多数项目自身也陷入启动资金少,宣发困难,新员增量和社区联动无法达到启动规模,圈内名人和打卡社团的高价广告费让小项目方成为第一波韭菜,项目的白名单公售信息无法传递于圈子,于是一个喜闻乐见的好工具Premint及时出现并帮助大量的

    2024年01月17日
    浏览(49)
  • C# &OpenCV 从零开发(0):前言

    由于我想换个机器视觉+运动控制的工作,我就开始了自学机器视觉方向的技术。但是Halcon毕竟是商业化的库,国内用盗版还是怕被告。所以期望使用OpenCV。 OpenCV目前已知的方法的有两个版本 Python:用起来挺简单的,就是Python的语言不适合管理,感觉以后必定会出现问题,不适

    2024年01月18日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包