MutationObserver的性能,用于检测整个DOM中的节点

问题描述 投票:49回答:1

我有兴趣使用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例子都是如此具体的目标容器......但我不确定。

javascript html html5 mutation-observers
1个回答
113
投票

This answer applies to big and complex pages.

特别是如果在页面开始加载之前附加了观察者(即Chrome扩展/ WebExtensions / userscripts中的document_start / document-start,或者只是在<head>内的正常同步页面脚本中),而且还包括大量动态更新的页面,例如分支比较GitHub。如果页面很大且复杂(12),未优化的MutationObserver回调可以为页面加载时间添加几秒钟。大多数示例和现有库都不考虑这种情况,并提供外观漂亮,易于使用但速度慢的js代码。

MutationObserver回调作为微任务执行,阻止进一步处理DOM,并且可以在复杂页面上每秒触发数百或数千次。

  1. 始终使用devtools profiler并尝试使观察者回调消耗不到页面加载期间消耗的总CPU时间的1%。
  2. 通过访问偏移顶部和类似属性来避免触发qazxsw poi
  3. 避免使用像jQuery这样的复杂DOM框架/库,更喜欢本机DOM的东西
  4. 观察属性时,请在forced synchronous layout中使用attributeFilter: ['attr1', 'attr2']选项。
  5. 在任何可能的情况下,非递归地观察直接父母(.observe())。 例如,通过递归地观察subtree: false来等待父元素是有意义的,在成功时断开观察者的连接,在这个容器元素上附加一个新的非递归的。
  6. 当等待一个具有document属性的元素时,使用疯狂快速的id而不是枚举getElementById数组(它可能有数千个条目):mutations
  7. 如果页面上所需的元素相对稀少(例如exampleiframe),请使用objectgetElementsByTagName返回的实时HTMLCollection,并重新检查它们,而不是枚举getElementsByClassName,例如,如果它有超过100个元素。
  8. 避免使用mutations,特别是极慢的querySelector
  9. 如果在MutationObserver回调中绝对不可避免地使用querySelectorAll,首先执行querySelectorAll检查,如果成功,则继续使用querySelector。平均而言,这样的组合会快得多。
  10. 如果针对非出血边缘浏览器,不要使用需要回调的内置数组方法,如forEach,filter等,因为在Chrome的V8中,与经典的querySelectorAll循环相比,这些函数的调用一直很昂贵(10-100次)更慢,但V8团队正在努力[2017]),并且MutationObserver回调可能会在复杂的现代页面上的每批突变中每秒发射100次,数千,数百或数千个for (var i=0 ....)。 数组内置函数的内联不是通用的,它通常发生在类似基准的原始代码中。在现实世界中,MutationObserver具有间歇性的活动峰值(如1-1000个节点每秒报告100次),并且回调永远不会像addedNodes那样简单,因此代码不会被检测为“热”足以进行内联/优化。 由lodash或类似的快速库支持的替代功能枚举是可以的。截至2018年,Chrome和底层V8将内联标准阵列内置方法。
  11. 如果针对非出血边缘浏览器,请不要在MutationObserver回调中使用return x * x之类的the slow ES2015 loops,除非你进行转换,以便生成的代码运行速度与经典的for (let v of something)循环一样快。
  12. 如果目标是改变页面的外观,并且你有一个可靠而快速的方法来告诉所添加的元素在页面的可见部分之外,请断开观察者并通过for安排整个页面重新检查和重新处理:它将在执行时执行解析/布局活动的初始爆发已经完成,引擎可以“呼吸”,甚至可能需要一秒钟。然后,您可以使用requestAnimationFrame以不显眼的方式处理页面。例如。

Back to the question:

看一个非常确定的容器setTimeout(fn, 0),看看是否附加任何ul#my-list

由于<li>是一个直接的孩子,我们寻找增加的节点,唯一需要的选择是li(参见上面的建议#2)。

childList: true
© www.soinside.com 2019 - 2024. All rights reserved.