web-dev-qa-db-ja.com

DOM全体のノードを検出するMutationObserverのパフォーマンス

特定のHTML要素がHTMLページのどこかに追加されているかどうかを検出するために、MutationObserverを使用することに興味があります。たとえば、<li>はDOMのどこにでも追加されます。

これまでに見てきたすべての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の例がターゲットコンテナーに非常に固有である理由があるのではないかと考えましたが、よくわかりません。

55
Jake Wilson

この回答は、大きく複雑なページに適用されます。

特に、ページの読み込みを開始する前にオブザーバーがアタッチされている場合(つまり、document_start/document-start in Chrome extensions/WebExtensions/userscriptsまたは<head>内の通常の同期ページスクリプト内)だけでなく、巨大な動的更新GitHubでのブランチ比較などのページ。最適化されていないMutationObserverコールバックは、ページが大きく複雑な場合( 12 )。ほとんどの例と既存のライブラリは、そのようなシナリオを考慮しておらず、見栄えがよく、使いやすいが、遅いjsコードを提供します。

MutationObserverコールバックは、DOMのさらなる処理をブロックするマイクロタスクとして実行され、複雑なページで毎秒何百または何千回も起動できます。

  1. 常に devtools profiler を使用し、ページの読み込み中に消費されるCPU時間全体の1%未満をオブザーバーコールバックに消費させます。

  2. OffsetTopおよび類似のプロパティにアクセスして、 forced同期レイアウト をトリガーしないでください

  3. JQueryのような複雑なDOMフレームワーク/ライブラリの使用を避け、ネイティブDOMのものを好む

  4. 属性を観察するときは、.observe()attributeFilter: ['attr1', 'attr2']オプションを使用します。

  5. 可能な限り、直接の親を非再帰的に観察します(subtree: false)。
    たとえば、documentを再帰的に監視して親要素を待機し、成功したらオブザーバを切断し、このコンテナ要素に新しい非再帰的なものをアタッチすることは理にかなっています。

  6. id属性を持つ要素を1つだけ待機する場合、getElementById配列を列挙する代わりに、非常に高速なmutationsinsteadを使用します(数千のエントリがある場合があります): example

  7. 目的の要素がページ上で比較的まれな場合(iframeまたはobjectなど)、getElementsByTagNameおよびgetElementsByClassNameによって返されるライブHTMLCollectionを使用し、代わりにすべてを再チェックしますたとえば、mutationsに100を超える要素がある場合は列挙します。

  8. querySelector、特に非常に遅いquerySelectorAllの使用は避けてください。

  9. MutationObserverコールバック内でquerySelectorAllが絶対に避けられない場合は、最初にquerySelectorチェックを実行し、成功した場合はquerySelectorAllに進みます。平均すると、このようなコンボははるかに高速になります。

  10. 非ブリードエッジブラウザーを対象とする場合、ChromeのV8ではこれらの関数は従来のfor (var i=0 ....)ループと比較して呼び出しに常にコストがかかるため、コールバックを必要とするforEachやfilterなどの組み込みArrayメソッドを使用しないでください10〜100倍遅くなりますが、V8チームが取り組んでいます[2017])。MutationObserverコールバックは、複雑な現代ページの各バッチの突然変異で、数十、数百、または数千のaddedNodesで毎秒100回起動します。

    配列の組み込みのインライン化は普遍的ではなく、一般的にベンチマークのようなプリミティブコードで発生します。現実の世界では、MutationObserverには断続的なアクティビティのスパイク(1秒間に100回報告される1-1000ノードなど)があり、コールバックはreturn x * xほど単純ではないため、コードはインライン化/最適化できるほど「ホット」として検出されません。

    ただし、lodashまたは同様の高速ライブラリに裏付けられた代替の関数列挙は大丈夫です。 2018年の時点でChromeであり、基礎となるV8は標準配列組み込みメソッドをインライン化します。

  11. 非ブリードエッジブラウザーを対象とする場合、結果のコードが高速で実行されるようにトランスパイルしない限り、MutationObserverコールバック内で 遅いES2015ループ のようなfor (let v of something)を使用しないでください。古典的なforループとして。

  12. ページの外観を変更することが目的であり、追加する要素がページの表示部分の外にあることを確実かつ高速に通知する方法がある場合は、オブザーバーを切断し、setTimeout(fn, 0):itを介してページ全体の再チェックと再処理をスケジュールします解析/レイアウトアクティビティの最初のバーストが終了し、エンジンが「ブリージング」できるようになると、1秒もかかることがあります。次に、たとえばrequestAnimationFrameを使用して、ページをチャンクで目立たないように処理できます。

質問に戻る:

非常に特定のコンテナ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});
118
wOxxOm