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

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

特定の HTML 要素が HTML ページのどこかに追加されたかどうかを検出するためにを使用することに興味があります。例として、 DOM のMutationObserverどこかに が追加されているかどうかを検出したいとします。<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、よくわかりません。

ベストアンサー1

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

ページの読み込み/レンダリングの前にアタッチされた場合、最適化されていない MutationObserver コールバックは、ページが大きく複雑な場合、ページの読み込み時間に数秒 (たとえば、5 秒から 7 秒) 追加する可能性があります (12)。コールバックは、DOM のさらなる処理をブロックするマイクロタスクとして実行され、複雑なページでは 1 秒間に数百回または数千回実行される可能性があります。ほとんどの例と既存のライブラリは、このようなシナリオを考慮しておらず、見栄えがよく使いやすいものの、遅くなる可能性のある JS コードを提供しています。

  1. 常に開発ツールプロファイラーオブザーバー コールバックがページの読み込み中に費やされる CPU 時間の全体のうち 1% 未満を消費するようにしてください。

  2. トリガーリングを避ける強制同期レイアウトoffsetTopや類似のプロパティにアクセスすることで

  3. jQueryのような複雑なDOMフレームワークやライブラリの使用は避け、ネイティブDOMのものを使用するようにしてください。

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

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

  6. 属性を持つ要素を1つだけ待つ場合はid、非常に高速なgetElementById その代わり配列を列挙する方法mutations(エントリが数千個ある場合があります):

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

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

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

  10. 2018 年以前の Chrome/ium をターゲットとする場合は、コールバックを必要とする forEach、filter などの組み込み配列メソッドを使用しないでください。これは、Chrome の V8 では、これらの関数の呼び出しは従来のfor (var i=0 ....)ループに比べて常にコストがかかり (10 ~ 100 倍遅い)、MutationObserver コールバックは複雑な最新のページで数千のノードを報告する可能性があるためです。

  • lodash または同様の高速ライブラリによってサポートされる代替機能列挙は、古いブラウザでも問題ありません。
  • 2018 年現在、Chrome/ium は標準の配列組み込みメソッドをインライン化しています。
  1. 2019年以前のブラウザをターゲットにする場合は、使用しないでください。遅いES2015ループfor (let v of something)結果のコードが従来のループと同じ速度で実行されるようにトランスパイルしない限り、MutationObserver コールバック内のようにはなりませんfor

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

  3. 処理が複雑で時間がかかる場合、ペイントフレームが非常に長くなったり、応答がなくなったり、ジャンクが発生する可能性があるため、この場合はデバウンスまたは同様のテクニック、例えば外側の配列に変化を蓄積し、setTimeout / requestIdleCallback / requestAnimationFrame を介して実行をスケジュールする:

    const queue = [];
    const mo = new MutationObserver(mutations => {
      if (!queue.length) requestAnimationFrame(process);
      queue.push(mutations);
    });
    function process() {
      for (const mutations of queue) {
        // ..........
      }
      queue.length = 0;
    }
    

    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});

おすすめ記事