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
节点中发生的节点的新增与删除(同时,如果subtree
为true
,会针对整个子树生效)。默认值为false
。attributes 可选 当为 true
时观察所有监听的节点属性值的变化。默认值为true
, 当声明了attributeFilter
或attributeOldValue
,默认值为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.js
、prism.min.css
以及根据自己要高亮的语言选择对应的 js
(示例中使用prism-javascript.min.js
); - 在
prism.min.js
的script标签
上添加
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