MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例

简介: MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~


作者:watermelo37

涉及领域:Vue、SpingBoot、Docker、LLM、python等

---------------------------------------------------------------------

温柔地对待温柔的人,包容的三观就是最大的温柔。

---------------------------------------------------------------------

image.gif 编辑

深入理解 JavaScript 中的 MutationObserver:原理与实战案例

       在前端基于Vue的开发中,我们会用watch来监听数据的变化,甚至还可以通过deep属性的配置项来监听对象内部的变化(侦听器watch用法详解,vue2与vue3中watch的变化与差异),我们也知道在 Vue2 中 watch 的底层是通过 Vue 中的一个叫做 hasChange 的函数来判断参数是否变化,而深层监听则是添加了遍历的操作,在 Vue3 中由于使用 proxy 替代了defineProperty ,便利程度大大提高。可是如果我们想监听的不只是数据的变化,而涉及到DOM的变化呢?

image.gif 编辑

       JavaScript 提供了多种 API 来操作 DOM 结构。而在操作 DOM 时,我们经常需要监测 DOM 的变化,这时候,MutationObserver 就显得格外有用。在这篇博客中,我们将详细介绍 MutationObserver 的工作原理,并通过几个实战案例帮助你全面掌握如何在实际项目中使用 MutationObserver。

        同时mutationObserver在事件循环中会放入微队列,拥有最高优先级的执行顺序,什么是事件循环?JS实现异步的基础是什么?具体内容详见:最细最有条理解析:事件循环(消息循环)是什么?为什么JS需要异步

一、MutationObserver 简介

       MutationObserver 是 HTML5 引入的一种用于监听 DOM 树变化的接口。它可以在 DOM 树发生以下变化时执行回调函数:

  1. 元素的子树发生变化(子节点的添加、删除或重排序)。
  2. 元素的属性发生变化。
  3. 元素的文本内容发生变化。

       与传统的 DOM 事件(如 DOMSubtreeModified、DOMNodeInserted、DOMNodeRemoved 等)相比,MutationObserver 提供了更高效和更灵活的 API。

二、MutationObserver 的工作原理

       MutationObserver 通过异步方式监测 DOM 变化,这意味着当 DOM 变化发生时,MutationObserver 不会立即执行回调函数,而是将这些变化存入一个队列中,并在本轮 JavaScript 执行完之后,才批量处理这些变化。这种异步批量处理的机制,使得 MutationObserver 更加高效。

1、基本用法

       使用 MutationObserver 的基本步骤如下:

  • 创建一个 MutationObserver 实例,传入一个回调函数。
  • 使用 observe 方法开始监听目标节点及其相关的变化。
  • 当不再需要监听时,使用 disconnect 方法停止观察。
// 1. 创建一个 MutationObserver 实例,并传入回调函数
const observer = new MutationObserver((mutationsList, observer) => {
  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.');
    }
  }
});
// 2. 开始监听目标节点
const targetNode = document.getElementById('myElement');
const config = { attributes: true, childList: true, subtree: true };
observer.observe(targetNode, config);
// 3. 停止监听
// observer.disconnect();

image.gif

2、observe 方法的配置项

       observe 方法接受两个参数:目标节点和一个配置对象。配置对象用于指定要观察哪些类型的变化。常用配置项包括:

  • attributes: 当元素的属性变化时触发回调(默认为 false)。
  • childList: 当目标节点的子节点被添加或删除时触发回调(默认为 false)。
  • subtree: 当设置为 true 时,监视目标节点及其所有后代节点的变化(默认为 false)。
  • characterData: 当节点的文本内容变化时触发回调(默认为 false)。
  • attributeOldValue: 当属性变化时,记录变化前的属性值(默认为 false)。
  • characterDataOldValue: 当文本节点变化时,记录变化前的文本内容(默认为 false)。
  • attributeFilter: 一个属性名称的数组,指定监听的属性变化(如果不设置,则监听所有属性)。

三、实战案例

       通过以下几个案例,我们来探讨 MutationObserver 的实际应用场景和优势。

案例 1:监控动态内容加载

       在一些 SPA(单页应用)中,内容是通过 AJAX 动态加载到页面上的。我们可以使用 MutationObserver 监控这些动态内容的加载,并在加载完成后进行一些操作(如绑定事件、修改样式等)

// 动态内容加载容器
const contentContainer = document.getElementById('content');
// 创建观察者实例
const observer = new MutationObserver((mutationsList) => {
  for (let mutation of mutationsList) {
    if (mutation.type === 'childList') {
      console.log('New content has been loaded:', mutation.addedNodes);
      // 对新增的节点进行一些操作
      mutation.addedNodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE) {
          // 绑定事件,或执行其他逻辑
          node.addEventListener('click', () => console.log('New element clicked!'));
        }
      });
    }
  }
});
// 配置选项
const config = { childList: true, subtree: true };
// 开始监控
observer.observe(contentContainer, config);

image.gif

       在这个案例中,我们监控一个动态内容加载容器,当新的子节点被添加到容器中时,我们对新增的节点绑定点击事件。

案例 2:监控属性变化

       假设我们有一个需求,需要在某个元素的 data-status 属性发生变化时做出响应。MutationObserver 可以轻松实现这一需求。

// 目标节点
const statusElement = document.getElementById('status');
// 创建观察者实例
const observer = new MutationObserver((mutationsList) => {
  for (let mutation of mutationsList) {
    if (mutation.type === 'attributes' && mutation.attributeName === 'data-status') {
      console.log('Status changed to:', statusElement.getAttribute('data-status'));
    }
  }
});
// 配置选项
const config = { attributes: true, attributeFilter: ['data-status'] };
// 开始监控
observer.observe(statusElement, config);

image.gif

案例 3:防止 DOM 劫持

       在一些恶意脚本或第三方插件注入的情况下,DOM 结构可能会被劫持。我们可以使用 MutationObserver 检测 DOM 结构的异常变化,从而做出防护措施。

// 目标节点
const body = document.body;
// 创建观察者实例
const observer = new MutationObserver((mutationsList) => {
  for (let mutation of mutationsList) {
    if (mutation.type === 'childList') {
      mutation.addedNodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT') {
          console.warn('Potentially malicious script detected!');
          // 移除恶意脚本
          node.parentNode.removeChild(node);
        }
      });
    }
  }
});
// 配置选项
const config = { childList: true, subtree: true };
// 开始监控
observer.observe(body, config);

image.gif

案例 4:优化页面性能

       有时候我们可能需要对大量 DOM 操作进行批量处理,而不是每次操作都立即反应。MutationObserver 可以用来检测批量 DOM 变化并集中处理,从而优化页面性能。

       在这个例子中,我们批量向容器中添加了 1000 个子节点,而 MutationObserver 会统一处理这些 DOM 变化,有效减少了重绘和重排操作。

// 目标节点
const container = document.getElementById('container');
// 创建观察者实例
const observer = new MutationObserver((mutationsList) => {
  mutationsList.forEach(mutation => {
    if (mutation.type === 'childList') {
      // 批量处理逻辑
      console.log('Batch processing DOM changes');
    }
  });
});
// 配置选项
const config = { childList: true, subtree: true };
// 开始监控
observer.observe(container, config);
// 批量修改 DOM
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const newDiv = document.createElement('div');
  newDiv.textContent = `Item ${i}`;
  fragment.appendChild(newDiv);
}
container.appendChild(fragment);

image.gif

四、总结

       MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。

        只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

       更多优质内容,请关注:

       你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解

       通过array.filter()实现数组的数据筛选、数据清洗和链式调用

       极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图

       el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能

       干货含源码!如何用Java后端操作Docker(命令行篇)

       JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南

       PDF预览:利用vue3-pdf-app实现前端PDF在线展示

       Docker 入门全攻略:安装、操作与常用命令指南

       shpfile转GeoJSON且控制转化精度;如何获取GeoJSON?GeoJson结构详解

       巧用Array.forEach:简化循环与增强代码可读性

       通过array.reduce()实现数据汇总、条件筛选和映射、对象属性的扁平化、转换数据格式等

       Mapbox添加行政区矢量图层、分级设色图层、自定义鼠标悬浮框、添加天地图底图等

相关文章
|
3月前
|
存储 安全 API
Next.js 实战 (九):使用 next-auth 完成第三方身份登录验证
这篇文章介绍了next-auth,一个为Next.js设计的身份验证库,支持多种认证方式,如电子邮件和密码、OAuth2.0提供商(如Google、GitHub、Facebook等)以及自定义提供商。文章包含了如何配置Github Provider以及会话管理,并提到了适配器Adapters在next-auth中的作用。最后,文章强调了next-auth的强大功能值得进一步探索。
223 10
|
1月前
|
JavaScript 前端开发 Java
深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
Array.find() 是 JavaScript 数组方法中一个非常实用和强大的工具。它不仅提供了简洁的查找操作,还具有性能上的独特优势:返回的引用能够直接影响原数组的数据内容,使得数据更新更加高效。通过各种场景的展示,我们可以看到 Array.find() 在更新、条件查找和嵌套结构查找等场景中的广泛应用。 在实际开发中,掌握 Array.find() 的特性和使用技巧,可以让代码更加简洁高效,特别是在需要直接修改原数据内容的情形。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
2月前
|
JavaScript 前端开发 索引
40个JS常用使用技巧案例
大家好,我是V哥。在日常开发中,JS是解决页面交互的利器。V哥总结了40个实用的JS小技巧,涵盖数组操作、对象处理、函数使用等,并附带案例代码和解释。从数组去重到异步函数,这些技巧能显著提升开发效率。先赞再看后评论,腰缠万贯财进门。关注威哥爱编程,全栈开发就你行!
92 16
|
1月前
|
JavaScript 前端开发 Java
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
柯里化是一种强大的函数式编程技术,它通过将函数分解为单参数形式,实现了灵活性与可复用性的统一。无论是参数复用、延迟执行,还是函数组合,柯里化都为现代编程提供了极大的便利。 从 Redux 的选择器优化到复杂的数据流处理,再到深度嵌套的函数优化,柯里化在实际开发中展现出了非凡的价值。如果你希望编写更简洁、更优雅的代码,柯里化无疑是一个值得深入学习和实践的工具。从简单的实现到复杂的应用,希望这篇博客能为你揭开柯里化的奥秘,助力你的开发之旅! 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
3月前
|
设计模式 数据安全/隐私保护
Next.js 实战 (七):浅谈 Layout 布局的嵌套设计模式
这篇文章介绍了在Next.js框架下,如何处理中后台管理系统中特殊页面(如登录页)不包裹根布局(RootLayout)的问题。作者指出Next.js的设计理念是通过布局的嵌套来创建复杂的页面结构,这虽然保持了代码的整洁和可维护性,但对于特殊页面来说,却造成了不必要的布局包裹。文章提出了一个解决方案,即通过判断页面的skipGlobalLayout属性来决定是否包含RootLayout,从而实现特殊页面不包裹根布局的目标。
160 33
|
3月前
|
中间件 API
Next.js 实战 (八):使用 Lodash 打包构建产生的“坑”?
这篇文章介绍了作者在使用Nextjs15进行项目开发时遇到的部署问题。在部署过程中,作者遇到了打包构建时的一系列报错,报错内容涉及动态代码评估在Edge运行时不被允许等问题。经过一天的尝试和调整,作者最终删除了lodash-es库,并将radash的部分源码复制到本地,解决了打包报错的问题。文章最后提供了项目的线上预览地址,并欢迎读者留言讨论更好的解决方案。
86 10
|
4月前
|
前端开发 API 开发者
Next.js 实战 (五):添加路由 Transition 过渡效果和 Loading 动画
这篇文章介绍了Framer Motion,一个为React设计的动画库,提供了声明式API处理动画和页面转换,适合创建响应式用户界面。文章包括首屏加载动画、路由加载Loading、路由进场和退场动画等主题,并提供了使用Framer Motion和next.js实现这些动画的示例代码。最后,文章总结了这些效果,并邀请读者探讨更好的实现方案。
117 7
|
3月前
|
JavaScript 前端开发 API
Next.js 实战 (六):如何实现文件本地上传
这篇文章介绍了在Next.js中如何实现文件上传到本地的方法。文章首先提到Next.js官方文档中没有提供文件上传的实例代码,因此开发者需要自行实现,通常有两种思路:使用Node.js原生上传或使用第三方插件如multer。接着,文章选择了使用Node.js原生上传的方式来讲解实现过程,包括如何通过哈希值命名文件、上传到指定目录以及如何分类文件夹。然后,文章展示了具体的实现步骤,包括编写代码来处理文件上传,并给出了代码示例。最后,文章通过一个效果演示说明了如何通过postman模拟上传文件,并展示了上传后的文件夹结构。
|
4月前
Next.js 实战 (二):搭建 Layouts 基础排版布局
本文介绍了作者在Next.js v15.x版本发布后,对一个旧项目的重构过程。文章详细说明了项目开发规范配置、UI组件库选择(最终选择了Ant-Design)、以及使用Ant Design的Layout组件实现中后台布局的方法。文末展示了布局的初步效果,并提供了GitHub仓库链接供读者参考学习。
141 1
Next.js 实战 (二):搭建 Layouts 基础排版布局
|
3月前
|
监控 安全 中间件
Next.js 实战 (十):中间件的魅力,打造更快更安全的应用
这篇文章介绍了什么是Next.js中的中间件以及其应用场景。中间件可以用于处理每个传入请求,比如实现日志记录、身份验证、重定向、CORS配置等功能。文章还提供了一个身份验证中间件的示例代码,以及如何使用限流中间件来限制同一IP地址的请求次数。中间件相当于一个构建模块,能够简化HTTP请求的预处理和后处理,提高代码的可维护性,有助于创建快速、安全和用户友好的Web体验。
下一篇
oss创建bucket