MutationObserver
是一个内置对象,它观察 DOM 元素并在检测到更改时触发回调函数。
我们首先看一下语法,然后探索一个实际用例,看看这种东西在什么地方有用。
语法
MutationObserver
很容易使用。
首先,我们使用回调函数创建一个观察者
let
observer =
new
MutationObserver
(
callback)
;
然后将其附加到 DOM 节点
observer.
observe
(
node,
config)
;
config
是一个包含布尔选项的对象,用于指定“对哪些类型的更改做出反应”
childList
–node
的直接子节点的更改,subtree
–node
的所有后代的更改,attributes
–node
的属性,attributeFilter
– 属性名称数组,用于仅观察选定的属性。characterData
– 是否观察node.data
(文本内容),
其他一些选项
attributeOldValue
– 如果为true
,则将属性的旧值和新值都传递给回调(见下文),否则仅传递新值(需要attributes
选项),characterDataOldValue
– 如果为true
,则将node.data
的旧值和新值都传递给回调(见下文),否则仅传递新值(需要characterData
选项)。
然后,在任何更改之后,都会执行 callback
:更改作为 MutationRecord 对象列表传递给第一个参数,观察者本身作为第二个参数传递。
MutationRecord 对象具有以下属性
type
– 变异类型,以下之一"attributes"
: 属性已修改"characterData"
: 数据已修改,用于文本节点,"childList"
: 子元素已添加/删除,
target
– 更改发生的位置:对于"attributes"
来说是元素,对于"characterData"
来说是文本节点,对于"childList"
变异来说是元素,addedNodes/removedNodes
– 已添加/删除的节点,previousSibling/nextSibling
– 已添加/删除节点的先前和下一个兄弟节点,attributeName/attributeNamespace
– 已更改属性的名称/命名空间(对于 XML),oldValue
– 上一个值,仅用于属性或文本更改,如果相应的选项设置为attributeOldValue
/characterDataOldValue
。
例如,这里有一个带有 contentEditable
属性的 <div>
。该属性允许我们聚焦它并进行编辑。
<
div
contentEditable
id
=
"
elem"
>
Click and <
b
>
edit</
b
>
, please</
div
>
<
script
>
let
observer =
new
MutationObserver
(
mutationRecords
=>
{
console.
log
(
mutationRecords)
;
// console.log(the changes)
}
)
;
// observe everything except attributes
observer.
observe
(
elem,
{
childList:
true
,
// observe direct children
subtree:
true
,
// and lower descendants too
characterDataOldValue:
true
// pass old data to callback
}
)
;
</
script
>
如果我们在浏览器中运行这段代码,然后聚焦给定的 <div>
并更改 <b>edit</b>
内部的文本,console.log
将显示一个变异
mutationRecords =
[
{
type:
"characterData"
,
oldValue:
"edit"
,
target:
<
text node>
,
// other properties empty
}
]
;
如果我们进行更复杂的编辑操作,例如删除 <b>edit</b>
,变异事件可能包含多个变异记录
mutationRecords =
[
{
type:
"childList"
,
target:
<
div#elem>
,
removedNodes:
[
<
b>
]
,
nextSibling:
<
text node>
,
previousSibling:
<
text node>
// other properties empty
}
,
{
type:
"characterData"
target:
<
text node>
// ...mutation details depend on how the browser handles such removal
// it may coalesce two adjacent text nodes "edit " and ", please" into one node
// or it may leave them separate text nodes
}
]
;
因此,MutationObserver
允许对 DOM 子树中的任何更改做出反应。
集成用法
什么时候有用呢?
想象一下,你需要添加一个包含有用功能的第三方脚本,但它也做了一些你不想要的事情,例如显示广告 <div class="ads">不想要的广告</div>
。
当然,第三方脚本没有提供删除它的机制。
使用 MutationObserver
,我们可以检测到不想要的元素何时出现在我们的 DOM 中,并将其删除。
还有一些其他情况,第三方脚本会向我们的文档中添加一些内容,我们希望在它发生时检测到,以便调整我们的页面,动态调整某些内容的大小等等。
MutationObserver
允许实现这一点。
架构用法
还有一些情况,从架构的角度来看,MutationObserver
很好。
假设我们正在制作一个关于编程的网站。自然地,文章和其他材料可能包含源代码片段。
HTML 标记中的此类片段如下所示
...
<
pre
class
=
"
language-javascript"
>
<
code
>
// here's the code
let hello = "world";
</
code
>
</
pre
>
...
为了更好的可读性,同时为了美化它,我们将在我们的网站上使用一个 JavaScript 语法高亮库,比如 Prism.js。为了获得 Prism 中上述片段的语法高亮,调用 Prism.highlightElem(pre)
,它会检查此类 pre
元素的内容,并向这些元素添加用于彩色语法高亮的特殊标签和样式,类似于你在本页示例中看到的内容。
我们应该在什么时候运行高亮方法呢?好吧,我们可以在 DOMContentLoaded
事件中执行它,或者将脚本放在页面底部。当我们的 DOM 准备就绪时,我们可以搜索 pre[class*="language"]
元素,并在其上调用 Prism.highlightElem
// highlight all code snippets on the page
document.
querySelectorAll
(
'pre[class*="language"]'
)
.
forEach
(
Prism.
highlightElem)
;
到目前为止一切都很简单,对吧?我们在 HTML 中找到代码片段并突出显示它们。
现在让我们继续。假设我们要从服务器动态获取材料。我们将在 本教程的后面 学习相关方法。现在,我们只需要知道我们从 Web 服务器获取 HTML 文章并按需显示它。
let
article =
/* fetch new content from server */
articleElem.
innerHTML =
article;
新的 article
HTML 可能包含代码片段。我们需要在它们上调用 Prism.highlightElem
,否则它们将不会被高亮显示。
在哪里以及何时为动态加载的文章调用 Prism.highlightElem
呢?
我们可以将该调用附加到加载文章的代码中,如下所示
let
article =
/* fetch new content from server */
articleElem.
innerHTML =
article;
let
snippets =
articleElem.
querySelectorAll
(
'pre[class*="language-"]'
)
;
snippets.
forEach
(
Prism.
highlightElem)
;
…但是,想象一下,如果我们在代码中有很多地方加载我们的内容——文章、测验、论坛帖子等等。我们需要在每个地方都放置高亮调用,以便在加载后高亮显示内容中的代码吗?这很不方便。
如果内容是由第三方模块加载的呢?例如,我们有一个由其他人编写的论坛,它动态加载内容,我们想为它添加语法高亮。没有人喜欢修补第三方脚本。
幸运的是,还有另一种选择。
我们可以使用 MutationObserver
自动检测代码片段何时插入页面并突出显示它们。
因此,我们将集中在一个地方处理突出显示功能,从而免去我们集成它的麻烦。
动态突出显示演示
这是一个工作示例。
如果您运行此代码,它将开始观察下面的元素,并突出显示出现在那里的任何代码片段。
let
observer =
new
MutationObserver
(
mutations
=>
{
for
(
let
mutation of
mutations)
{
// examine new nodes, is there anything to highlight?
for
(
let
node of
mutation.
addedNodes)
{
// we track only elements, skip other nodes (e.g. text nodes)
if
(
!
(
node instanceof
HTMLElement
)
)
continue
;
// check the inserted element for being a code snippet
if
(
node.
matches
(
'pre[class*="language-"]'
)
)
{
Prism.
highlightElement
(
node)
;
}
// or maybe there's a code snippet somewhere in its subtree?
for
(
let
elem of
node.
querySelectorAll
(
'pre[class*="language-"]'
)
)
{
Prism.
highlightElement
(
elem)
;
}
}
}
}
)
;
let
demoElem =
document.
getElementById
(
'highlight-demo'
)
;
observer.
observe
(
demoElem,
{
childList:
true
,
subtree:
true
}
)
;
这里,下面,有一个 HTML 元素和 JavaScript,它使用 innerHTML
动态填充它。
请运行前面的代码(上面,观察该元素),然后运行下面的代码。您将看到 MutationObserver
如何检测和突出显示代码片段。
一个带有 id="highlight-demo"
的演示元素,运行上面的代码来观察它。
以下代码填充其 innerHTML
,这会导致 MutationObserver
反应并突出显示其内容。
let
demoElem =
document.
getElementById
(
'highlight-demo'
)
;
// dynamically insert content with code snippets
demoElem.
innerHTML =
`
A code snippet is below:
<pre class="language-javascript"><code> let hello = "world!"; </code></pre>
<div>Another one:</div>
<div>
<pre class="language-css"><code>.class { margin: 5px; } </code></pre>
</div>
`
;
现在我们有了 MutationObserver
,它可以跟踪观察到的元素或整个 document
中的所有突出显示。我们可以在 HTML 中添加/删除代码片段,而无需考虑它。
其他方法
有一种方法可以停止观察节点。
observer.disconnect()
– 停止观察。
当我们停止观察时,可能某些更改尚未由观察者处理。在这种情况下,我们使用
observer.takeRecords()
– 获取未处理的变异记录列表——那些已经发生但回调尚未处理的记录。
这些方法可以一起使用,如下所示
// get a list of unprocessed mutations
// should be called before disconnecting,
// if you care about possibly unhandled recent mutations
let
mutationRecords =
observer.
takeRecords
(
)
;
// stop tracking changes
observer.
disconnect
(
)
;
...
observer.takeRecords()
返回的记录将从处理队列中删除。回调不会针对由 observer.takeRecords()
返回的记录调用。
观察者在内部使用对节点的弱引用。也就是说,如果一个节点从 DOM 中删除,并且变得不可达,那么它就可以被垃圾回收。
仅仅因为一个 DOM 节点被观察并不意味着它不会被垃圾回收。
总结
MutationObserver
可以对 DOM 中的更改做出反应——属性、文本内容以及添加/删除元素。
我们可以使用它来跟踪由我们代码的其他部分引入的更改,以及与第三方脚本集成。
MutationObserver
可以跟踪任何更改。配置“要观察的内容”选项用于优化,而不是将资源浪费在不必要的回调调用上。