我有兴趣使用MutationObserver
来检测是否在HTML页面中的任何位置添加了某个HTML元素。例如,我会说我想检测是否在DOM中的任何地方添加了任何<li>
。
到目前为止,我见过的所有MutationObserver
示例仅检测是否将节点添加到特定容器中。例如:
一些HTML
<body>
...
<ul id='my-list'></ul>
...
</body>
MutationObserver
的定义
var container = document.querySelector('ul#my-list');
var observer = new MutationObserver(function(mutations){
// Do something here
});
observer.observe(container, {
childList: true,
attributes: true,
characterData: true,
subtree: true,
attributeOldValue: true,
characterDataOldValue: true
});
所以在这个例子中,MutationObserver
被设置为观察一个非常确定的容器(ul#my-list
),看看是否有任何<li>
附加到它。
这是一个问题,如果我想要不那么具体,并注意<li>
在整个HTML体上像这样:
var container = document.querySelector('body');
我知道它适用于我为自己设置的基本示例......但是不建议这样做吗?这会导致性能不佳吗?如果是这样,我将如何检测和衡量性能问题?
我想也许有一个原因,所有的MutationObserver
例子都是如此具体的目标容器......但我不确定。
特别是如果在页面开始加载之前附加了观察者(即Chrome扩展/ WebExtensions / userscripts中的document_start
/ document-start
,或者只是在<head>
内的正常同步页面脚本中),而且还包括大量动态更新的页面,例如分支比较GitHub。如果页面很大且复杂(1,2),未优化的MutationObserver回调可以为页面加载时间添加几秒钟。大多数示例和现有库都不考虑这种情况,并提供外观漂亮,易于使用但速度慢的js代码。
MutationObserver回调作为微任务执行,阻止进一步处理DOM,并且可以在复杂页面上每秒触发数百或数千次。
attributeFilter: ['attr1', 'attr2']
选项。.observe()
)。
例如,通过递归地观察subtree: false
来等待父元素是有意义的,在成功时断开观察者的连接,在这个容器元素上附加一个新的非递归的。document
属性的元素时,使用疯狂快速的id
而不是枚举getElementById
数组(它可能有数千个条目):mutations
。iframe
),请使用object
和getElementsByTagName
返回的实时HTMLCollection,并重新检查它们,而不是枚举getElementsByClassName
,例如,如果它有超过100个元素。mutations
,特别是极慢的querySelector
。querySelectorAll
,首先执行querySelectorAll
检查,如果成功,则继续使用querySelector
。平均而言,这样的组合会快得多。querySelectorAll
循环相比,这些函数的调用一直很昂贵(10-100次)更慢,但V8团队正在努力[2017]),并且MutationObserver回调可能会在复杂的现代页面上的每批突变中每秒发射100次,数千,数百或数千个for (var i=0 ....)
。
数组内置函数的内联不是通用的,它通常发生在类似基准的原始代码中。在现实世界中,MutationObserver具有间歇性的活动峰值(如1-1000个节点每秒报告100次),并且回调永远不会像addedNodes
那样简单,因此代码不会被检测为“热”足以进行内联/优化。
由lodash或类似的快速库支持的替代功能枚举是可以的。截至2018年,Chrome和底层V8将内联标准阵列内置方法。return x * x
之类的the slow ES2015 loops,除非你进行转换,以便生成的代码运行速度与经典的for (let v of something)
循环一样快。for
安排整个页面重新检查和重新处理:它将在执行时执行解析/布局活动的初始爆发已经完成,引擎可以“呼吸”,甚至可能需要一秒钟。然后,您可以使用requestAnimationFrame以不显眼的方式处理页面。例如。看一个非常确定的容器
setTimeout(fn, 0)
,看看是否附加任何ul#my-list
。
由于<li>
是一个直接的孩子,我们寻找增加的节点,唯一需要的选择是li
(参见上面的建议#2)。
childList: true