你不知道的 MutationObserver

MutationObserver 是什么?

MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。
利用 MutationObserver API 我们可以监视 DOM 的变化。DOM 的任何变化,比如节点的增加、减少、属性的变动、文本内容的变动通过这个 API 我们都可以得到通知

MutationObserver 特点:

  • 异步触发方式:它等待所有脚本任务执行完成后,才会运行,它是异步触发的。即会等待当前所有 DOM 操作都结束才触发,这样设计是为了应对 DOM 频繁变动的问题。
  • 它把 DOM 变动记录封装成一个数组进行统一处理而不是一条一条进行处理,变动是通过一个数组记录下来的。
  • 它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。

MutationObserver API 简介

它的兼容性

从上图可知,目前主流的web浏览器都支持 MutationObserver API, 而 IE浏览器只有 IE 11 才支持。

使用

构造函数

MutationObserver():创建并返回一个新的 MutationObserver 它会在指定的 DOM 发生变化时被调用。 构造函数的语法为:

javascript 复制代码
    const observer = new MutationObserver(callback);

相关参数说明:
callback: 一个回调函数,每当被指定的节点或子树有发生 DOM 变动时,会被调用。该回调函数包含两个参数:一个是描述所有被触发改动的 MutationRecord 对象数组,另一个是调用该函数的 MutationObserver 对象。

javascript 复制代码
 const observer = new MutationObserver(function(mutations, observer) {
        mutations.forEach(function(mutation) {
            console.log(mutation)
        })
       })

实例方法

  • disconnect(): 阻止 MutationObserver 实例继续接收的通知,直到再次调用其 observe() 方法,该观察者对象包含的回调函数都不会再被调用。

  • observe(target[,options]): 配置 MutationObserver 在 DOM 更改 匹配给定选项时,通过其回调函数开始接收通知。该方法用来启动监听,它接收两个参数。第一个参数用于指定所有观察的 DOM 节点第二个参数,是一个配置对象,用于指定所要观察的特定变动(需要观察哪种类型的变动)

    • target: DOM 树中的一个要观察变化的 DOM node (可能是一个 Element), 或者是被观察的子节点树的根节点。

    • options: 观察配置属性,此对象的配置项描述了 DOM 的哪些变化应该报告给 MutationObserver 的 callback当调用 observe() 时, chilList、attributes 和 characterData 中,必须有一个参数为 true否则会抛出 TypeError 异常

      options 详细属性信息如下:

      属性 配置 描述
      subtree 可选 当为true时,将会监听以target为根节点的整个子树。包括子树中所有节点的属性,而不仅仅是针对 target。默认值为 false
      childList 可选 当为true时,监听target节点中发生的节点的新增与删除(同时,如果subtreetrue,会针对整个子树生效)。默认值为false
      attributes 可选 当为true时观察所有监听的节点属性值的变化。默认值为true, 当声明了attributeFilterattributeOldValue,默认值为false
      attributeFilter 可选 一个用于声明哪些属性名会被监听的数组。如果不声明该属性,所有属性的变化都将触发通知。
      attributeOldValue 可选 当为true时,记录上一次被监听的节点的属性变化;默认值为false
      characterData 可选 当为true时,监听声明的target节点上所有字符的变化。默认值为true, 如果声明了characterDataOldValue,默认值则为false
      characterDataOldValue 可选 当为true时,记录前一个被监听的节点中发生的文本变化。默认值为false。
  • takeRecords(): 从 MutationObserver 的通知队列中删除所有待处理的通知,并将它们返回到 MutationRecord 对象的新 Array 中。返回已检测到 但尚未由观察者的回调函数处理的所有匹配的 DOM 更改的类别,使变更队列保存为空。此方法最常见的使用场景是 在断开观察者之前立即获取所有未处理的更改记录,以便在停止观察者时可以处理任何未处理的更改

上述的参数配置以及基础语法,示例参考代码如下:

javascript 复制代码
       // 选择需要观察变动的节点
       // const targetNode = document.getElementById('editor')
       const targetNode = document.querySelector('#editor')

       // 观察器的配置(需要观察什么变动)
       const config = {
        childList: true, // 监视 node 直接子节点的变动
        subtree: true, // 监视 node 所有后代的变动
        attributes: true, // 监视 node 属性的变动
        characterData: true, // 监视指定目标节点或子节点树中节点所包含的字符串数据的变化。
        attributeOldValue: true, // 记录任何由改动的属性的旧值
       }

       // 1. 当观察到变动时执行的回调函数
       const callback = function (mutations, observer) {
        mutations.forEach(function(mutation) {
            console.log(mutation)
        })
       }
       
       // 2. 创建一个观察器
       const observer = new MutationObserver(callback)

       // 上述 1,2 也可以合并写
       // const observer = new MutationObserver(function (mutations, observer) {
       //     mutations.forEach(function(mutation) {
       //         console.log(mutation)
       //   })
       //  })

       // 开始观察目标节点,并传入观察器的配置。
       observer.observe(targetNode, config)

MutationRecord 对象
DOM 每次发生变化,就会生成一条变动记录。即 MutationRecord 实例。该实例包含了与变动相关的所有信息。 Mutation Observer 对象处理的就是一个个 MutationRecord 实例所组成的数组。 MutationRecord 实例包含了变动相关的信息,含有以下属性:

属性 描述
type 变动的类型,值可以是 attributes(属性变化)、characterData(字符串变动) 或 childList(节点变动) ;
target 发生变动的 DOM 节点;
addedNodes 返回新增的 DOM 节点,如果没有节点被添加,则返回一个空的 NodeList ;
removedNodes 返回移除的 DOM 节点,如果没有节点被移除,则返回一个空的 NodeList ;
previousSibling 返回被添加 或 移除的节点之前的兄弟节点,如果没有则返回 null ;
nextSibling 返回被添加 或 移除的节点之后的兄弟节点,如果没有则返回 null ;
attributeName 返回被修改的属性的属性名,如果设置了 attributeFilter , 则只返回预先指定的属性 ;
attributeNamespace 返回被修改属性的命名空间 ;
oldValue 变动的值。这个属性只对 attribute 和 characterData 变动有效,如果发生 childList 变动,则返回 null ;

使用示例

可编辑的 div <div contenteditable id="container" class="editor">MutationObserver</div>,感受下 MutationObserver 是什么,以及 变动记录中的属性都有什么。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MutationObserver</title>
    <style>
        .editor {
            width: 300px;
            height: 200px;
            border: 1px solid skyblue;
        }

    </style>
</head>
<body>
    <h3> DOM 变动观察器 MutationObserver </h3>
    <div contenteditable id="container" class="editor">MutationObserver</div>
    <script>
       // 获取目标节点
        const containerEle = document.querySelector('#container')
        // 创建观察器
        let observer = new MutationObserver((mutationRecord, observer) => {
            console.log("mutationRecord", mutationRecord)
            console.log("observer", observer)
        })
        // 开启目标节点的监视,并配置需要监视哪些变动。
        observer.observe(containerEle, {
            subtree: true, // 监视 node 所有 后代的变动
            characterDataOldValue: true, // 记录任何有变动的属性的旧值。
        })

    </script>
</body>
</html>

效果如下:

使用场景

语法高亮

Prism.js: Prism.js 语法高亮库docs:插件使用示例:here
使用 MutationObserver 结合 Prism.js 这个库实现 JavaScript 和 CSS 语法高亮

  • 找 prism 的cdn资源 : 到 cdn.bootcdn.net 查找对应的 prism.min.jsprism.min.css 以及 根据自己要高亮的语言选择对应的 js (示例中使用prism-javascript.min.js);
  • prism.min.jsscript标签添加 data-manual : 用于告诉 Prism 我们将使用手动模式来处理语法高亮。然后再 MutationObserver 的回调中,通过获取 mutation 对象的 addedNodes 属性,来进一步获取新增的 DOM 节点。然后我们遍历新增的 DOM 节点,判断新增的 DOM 节点是否为代码片段,如果满足条件则进行高亮操作。否则没有设置 data-manual, prism 将帮助我们自动处理语法高亮。
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title> javascript 语法高亮显示</title>
    <style>

    </style>
    <link href="https://cdn.bootcdn.net/ajax/libs/prism/9000.0.1/themes/prism.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/prism/9000.0.1/prism.min.js" data-manual></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/prism/9000.0.1/components/"></script>
</head>
<body>
    <div id="code-container"></div>
    <script>
        let codeContainer = document.querySelector("#code-container")
        
        let observer = new MutationObserver(mutations => {
            for(let mutation of mutations) {
                // 获取新增的 DOM 节点
                for(let node of mutation.addedNodes) {
                    // 只处理 HTML 元素,跳过其他节点,比如文本节点
                    if (!(node instanceof HTMLElement)) continue;

                    // 检查插入的节点是否为代码片段
                    if (node.matches('pre[class*="language-"]')) {
                        Prism.highlightElement(node)
                    }

                    // 检查插入节点的子节点是否为代码片段
                    for (let elem of node.querySelectorAll('pre[class*="language-"]')) {
                        Prism.highlightElement(elem)
                    }
                }
            }
        })
        observer.observe(codeContainer, {childList: true, subtree: true})
        codeContainer.innerHTML = `下⾯是⼀个JavaScript代码段:
                    <pre class="language-javascript">
                        <code> let greeting = "⼤家好,这是 Prism 语法高亮显示"; </code>
                    </pre>
                    <div>另⼀个CSS代码段: </div>
                    <div>
                        <pre class="language-css">
                            <code>#code-container { border: 1px dashed grey; padding: 5px; }</code>
                        </pre>
                    </div>
                    `

    </script>
</body>
</html>

效果如下:

富文本编辑器

富文本编辑器的场景,比如我们希望在富文本编辑器中高亮 #符号 后的内容,这个时候我们就可以通过 MutationObserver API 来监听用户输入的内容,发现用户输入 #时自动对输入的内容进行高亮处理。 hash tag 高亮。

借助npm包(vue-hashtag-textarea):实现输入"#"高亮,这个包中也是使用 MutationObserver来实现的,查看这个包的源码:vue-hashtag-textarea

效果图如下:

局限性

  • 无法监听 CSS 的变化,如:改变元素的宽度、高度、位置等属性,都无法被 MutationObserver检测到。
  • 无法检测到通过 cloneNode() 操作插入的节点,节点需要完全重新渲染才能被 MutationObserver 检测到。
  • 无法检测到对 Text 节点的修改,因为 Text 节点的 nodeValue 属性 是一个字符串,而不是一个 DOM 元素。
  • 无法检测到通过 innerHTML 进行赋值的变化。 因为这种方式是直接对 DOM 节点进行赋值,而不是 通过 DOM 操作进行的。

参考

Web API 接口参考 MutationObserver : mdn web docs

多文本域输入"#"标志后高亮显示后面的内容: vue-hashtag-textarea
稳定、快速、免费的前端开源项目 CDN 加速服务 cdn.bootcdn.net

相关推荐
ekskef_sef7 分钟前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine64132 分钟前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云1 小时前
npm淘宝镜像
前端·npm·node.js
dz88i81 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr1 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
顾平安2 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网2 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工2 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染
不是鱼2 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js