MutationObserver
是一个内建对象,它观察 DOM 元素,并在检测到更改时触发回调。
我们将首先看一下语法,然后探究一个实际的用例,以了解它在什么地方有用。
MutationObserver
使用简单。
首先,我们创建一个带有回调函数的观察器:
let observer = new MutationObserver(callback);
然后将其附加到一个 DOM 节点:
observer.observe(node, config);
config
是一个具有布尔选项的对象,该布尔选项表示“将对哪些更改做出反应”:
childList
—— node
的直接子节点的更改,subtree
—— node
的所有后代的更改,attributes
—— node
的特性(attribute),attributeFilter
—— 特性名称数组,只观察选定的特性。characterData
—— 是否观察 node.data
(文本内容),其他几个选项:
attributeOldValue
—— 如果为 true
,则将特性的旧值和新值都传递给回调(参见下文),否则只传新值(需要 attributes
选项),characterDataOldValue
—— 如果为 true
,则将 node.data
的旧值和新值都传递给回调(参见下文),否则只传新值(需要 characterData
选项)。然后,在发生任何更改后,将执行“回调”:更改被作为一个 MutationRecord[1] 对象列表传入第一个参数,而观察器自身作为第二个参数。
MutationRecord[2] 对象具有以下属性:
type
—— 变动类型,以下类型之一:
"attributes"
:特性被修改了,
"characterData"
:数据被修改了,用于文本节点,
"childList"
:添加/删除了子元素。
target
—— 更改发生在何处:"attributes"
所在的元素,或 "characterData"
所在的文本节点,或 "childList"
变动所在的元素,
addedNodes/removedNodes
—— 添加/删除的节点,
previousSibling/nextSibling
—— 添加/删除的节点的上一个/下一个兄弟节点,
attributeName/attributeNamespace
—— 被更改的特性的名称/命名空间(用于 XML),
oldValue
—— 之前的值,仅适用于特性或文本更改,如果设置了相应选项 attributeOldValue
/characterDataOldValue
。
例如,这里有一个 <div>
,它具有 contentEditable
特性。该特性使我们可以聚焦和编辑元素。
<div contentEditable id="elem">Click and <b>edit</b>, please</div>
<script>
let observer = new MutationObserver(mutationRecords => {
console.log(mutationRecords); // console.log(the changes)
});
// 观察除了特性之外的所有变动
observer.observe(elem, {
childList: true, // 观察直接子节点
subtree: true, // 及其更低的后代节点
characterDataOldValue: true // 将旧的数据传递给回调
});
</script>
如果我们在浏览器中运行上面这段代码,并聚焦到给定的 <div>
上,然后更改 <b>edit</b>
中的文本,console.log
将显示一个变动:
mutationRecords = [{
type: "characterData",
oldValue: "edit",
target: <text node>,
// 其他属性为空
}];
如果我们进行更复杂的编辑操作,例如删除 <b>edit</b>
,那么变动事件可能会包含多个变动记录:
mutationRecords = [{
type: "childList",
target: <div#elem>,
removedNodes: [<b>],
nextSibling: <text node>,
previousSibling: <text node>
// 其他属性为空
}, {
type: "characterData"
target: <text node>
// ...变动的详细信息取决于浏览器如何处理此类删除
// 它可能是将两个相邻的文本节点 "edit " 和 ", please" 合并成一个节点,
// 或者可能将它们留在单独的文本节点中
}];
因此,MutationObserver
允许对 DOM 子树中的任何更改作出反应。
在什么时候可能有用?
想象一下,你需要添加一个第三方脚本,该脚本不仅包含有用的功能,还会执行一些我们不想要的操作,例如显示广告 <div class="ads">Unwanted ads</div>
。
当然,第三方脚本没有提供删除它的机制。
使用 MutationObserver
,我们可以监测到我们不需要的元素何时出现在我们的 DOM 中,并将其删除。
还有一些其他情况,例如第三方脚本会将某些内容添加到我们的文档中,并且我们希望检测出这种情况何时发生,以调整页面,动态调整某些内容的大小等。
MutationObserver
使我们能够实现这种需求。
从架构的角度来看,在某些情况下,MutationObserver
有不错的作用。
假设我们正在建立一个有关编程的网站。自然地,文章和其他材料中可能包含源代码段。
在 HTML 标记(markup)中的此类片段如下所示:
...
<pre class="language-javascript"><code>
// 这里是代码
let hello = "world";
</code></pre>
...
为了提高可读性,同时对其进行美化,我们将在我们的网站上使用 JavaScript 语法高亮显示库,例如 Prism.js[3]。为了使用 Prism 对以上代码片段进行语法高亮显示,我们调用了 Prism.highlightElem(pre)
,它会检查此类 pre
元素的内容,并为这些元素添加特殊的标签(tag)和样式,以进行彩色语法高亮显示,类似于你在本文的示例中看到的那样。
那么,我们应该在什么时候执行该高亮显示方法呢?我们可以在 DOMContentLoaded
事件中执行,或者将脚本放在页面的底部。DOM 就绪后,我们可以搜索元素 pre[class*="language"]
并对其调用 Prism.highlightElem
:
// 高亮显示页面上的所有代码段
document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem);
到目前为止,一切都很简单,对吧?我们找到 HTML 中的代码片段并高亮显示它们。
现在让我们继续。假设我们要从服务器动态获取资料。我们将 在本教程的后续章节[4] 中学习进行此操作的方法。目前,只需要关心我们从网络服务器获取 HTML 文章并按需显示:
let article = /* 从服务器获取新内容 */
articleElem.innerHTML = article;
新的 article
HTML 可能包含代码段。我们需要对其调用 Prism.highlightElem
,否则它们将不会被高亮显示。
对于动态加载的文章,应该在何处何时调用 Prism.highlightElem
?
我们可以将该调用附加到加载文章的代码中,如下所示:
let article = /* 从服务器获取新内容 */
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) {
// 检查新节点,有什么需要高亮显示的吗?
for(let node of mutation.addedNodes) {
// 我们只跟踪元素,跳过其他节点(例如文本节点)
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);
}
}
}
});
let demoElem = document.getElementById('highlight-demo');
observer.observe(demoElem, {childList: true, subtree: true});
下面有一个 HTML 元素,以及使用 innerHTML
动态填充它的 JavaScript。
请先运行前面那段代码(上面那段,观察元素),然后运行下面这段代码。你将看到 MutationObserver
是如何检测并高亮显示代码段的。
<p id="highlight-demo" style="border: 1px solid #ddd">一个具有 <code>id="highlight-demo"</code> 的示例元素,运行上面那段代码来观察它。</p>
下面这段代码填充了其 innerHTML
,这导致 MutationObserver
作出反应,并突出显示其内容:
let demoElem = document.getElementById('highlight-demo');
// 动态插入带有代码段的内容
demoElem.innerHTML = `下面是一个代码段:
<pre class="language-javascript"><code> let hello = "world!"; </code></pre>
<div>另一个代码段:</div>
<div>
<pre class="language-css"><code>.class { margin: 5px; } </code></pre>
</div>
`;
现在我们有了 MutationObserver
,它可以跟踪观察到的元素中的,或者整个 document
中的所有高亮显示。我们可以在 HTML 中添加/删除代码段,而无需考虑高亮问题。
有一个方法可以停止观察节点:
observer.disconnect()
—— 停止观察。当我们停止观察时,观察器可能尚未处理某些更改。在种情况下,我们使用:
observer.takeRecords()
—— 获取尚未处理的变动记录列表,表中记录的是已经发生,但回调暂未处理的变动。这些方法可以一起使用,如下所示:
// 如果你关心可能未处理的近期的变动
// 那么,应该在 disconnect 前调用获取未处理的变动列表
let mutationRecords = observer.takeRecords();
// 停止跟踪变动
observer.disconnect();
...
observer.takeRecords()
返回的记录被从处理队列中移除:回调函数不会被
observer.takeRecords()
返回的记录调用。
垃圾回收:
观察器在内部对节点使用弱引用。也就是说,如果一个节点被从 DOM 中移除了,并且该节点变得不可访问,那么它就可以被垃圾回收。
观察到 DOM 节点这一事实并不能阻止垃圾回收。
MutationObserver
可以对 DOM 的变化作出反应 —— 特性(attribute),文本内容,添加/删除元素。
我们可以用它来跟踪代码其他部分引入的更改,以及与第三方脚本集成。
MutationObserver
可以跟踪任何更改。config
“要观察的内容”选项用于优化,避免不必要的回调调用以节省资源。
[1]MutationRecord: https://dom.spec.whatwg.org/#mutationrecord
[2]MutationRecord: https://dom.spec.whatwg.org/#mutationrecord
[3]Prism.js: https://prismjs.com/
[4]在本教程的后续章节: https://zh.javascript.info/fetch
[5]React 官方文档推荐,与 MDN 并列的 JavaScript 学习教程: https://zh-hans.reactjs.org/docs/getting-started.html#javascript-resources
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/oWI5M8VbLASPDXY34d6N5A
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。