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

时间:2015-07-27 17:31:39

标签: javascript html html5 mutation-observers

我有兴趣使用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>附加到其中。

如果我想不那么具体,并且在整个HTML正文中注意<li>,就会出现问题:

var container = document.querySelector('body');

我知道它适用于我为自己设置的基本示例......但是不建议这样做吗?这会导致性能不佳吗?如果是这样,我将如何检测和衡量性能问题?

我想可能有一个原因,所有MutationObserver示例都是如此具体的目标容器......但我不确定。

1 个答案:

答案 0 :(得分:108)

此答案适用于大页面和复杂页面。

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

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

  1. 始终使用devtools profiler并尝试使观察者回调消耗不到页面加载期间消耗的总CPU时间的1%。

  2. 通过访问offsetTop和类似属性来避免触发forced synchronous layout

  3. 避免使用像jQuery这样的复杂DOM框架/库,更喜欢本机DOM的东西

  4. 观察属性时,请使用attributeFilter: ['attr1', 'attr2']中的.observe()选项。

  5. 尽可能非递归地观察直接父母(subtree: false) 例如,通过递归观察document来等待父元素是有意义的,在成功时断开观察者的连接,在这个容器元素上附加一个新的非递归的。

  6. 在等待只有一个id属性的元素时,请使用枚举getElementById数组的快速mutations 而不是(可能有数以千计的条目):example

  7. 如果所需元素在页面上相对罕见(例如iframeobject),请使用getElementsByTagNamegetElementsByClassName返回的实时HTMLCollection并重新检查例如,如果mutations包含超过100个元素,则所有代替枚举

  8. 避免使用querySelector,尤其是速度极慢的querySelectorAll

  9. 如果在MutationObserver回调中绝对不可避免querySelectorAll,请先执行querySelector检查,如果成功,请继续querySelectorAll。平均而言,这样的组合会快得多。

  10. 如果定位非出血边缘浏览器,请不要使用内置数组方法,例如forEach,filter等需要回调的方法,因为在Chrome的V8中这些功能一直很昂贵调用与经典for (var i=0 ....)循环相比(慢了10-100倍,但是V8团队正在研究它[2017]),并且MutationObserver回调可能每秒发射100次,有数十,数百或数千{{1}在复杂的现代页面上的每批突变中。

    数组内置函数的内联不是通用的,它通常发生在类似基准的原始代码中。在现实世界中,MutationObserver具有间歇性的活动峰值(如1-1000个节点每秒报告100次),并且回调从未像addedNodes那样简单,因此代码未被检测为&#34; hot&#34;足以内联/优化。

    由lodash或类似的快速库支持的替代功能枚举是可以的。截至2018年,Chrome和底层V8将内联标准阵列内置方法。

  11. 如果定位非出血边缘的浏览器,请不要在MutationObserver回调中使用the slow ES2015 loops之类的return x * x,除非您进行转换,以便生成的代码运行速度与经典for (let v of something)一样快for 1}}循环。

  12. 如果目标是改变页面的外观,并且您有一种可靠而快速的方法来告知正在添加的元素位于页面的可见部分之外,请断开观察者并安排整页重新检查和重新处理通过setTimeout(fn, 0):当解析/布局活动的初始爆发完成并且引擎可以“呼吸”时,它将被执行。甚至可能需要一秒钟。然后,您可以使用requestAnimationFrame以不显眼的方式处理页面。

  13. 回到问题:

      

    观看一个非常特定的容器ul#my-list,看看是否有<li>附加到其中。

    由于li是直接子节点,并且我们寻找添加的节点,所需的唯一选项childList: true(参见上面的建议#2)。

    new MutationObserver(function(mutations, observer) {
        // Do something here
    
        // Stop observing if needed:
        observer.disconnect();
    }).observe(document.querySelector('ul#my-list'), {childList: true});