97

MutationObserver特定の HTML 要素が HTML ページのどこかに追加されているかどうかを検出するために使用することに興味があります。<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 本文全体で 's を監視したい場合、それは問題ですか?

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

自分でセットアップした基本的な例で動作することはわかっています...しかし、これを行うことはお勧めできませんか? これにより、パフォーマンスが低下しますか? もしそうなら、そのパフォーマンスの問題をどのように検出して測定しますか?

すべての例が対象となるコンテナーに非常に具体的である理由があるのではないかと考えましたMutationObserver...しかし、よくわかりません。

4

1 に答える 1

258

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

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

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

  2. offsetTopおよび同様のプロパティにアクセスして、強制同期レイアウトのトリガーを回避する

  3. jQuery のような複雑な DOM フレームワーク/ライブラリの使用を避け、ネイティブの DOM を優先する

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

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

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

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

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

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

  10. 2018 年より前の Chrome/ium を対象とする場合は、コールバックを必要とする forEach、filter などの組み込みの Array メソッドを使用しないでください。Chrome の V8 では、これらの関数は従来のfor (var i=0 ....)ループ (10-100倍遅い)、MutationObserver コールバックは、複雑な最新のページで数千のノードを報告する場合があります。

  • 古いブラウザーでも、lodash または同様の高速ライブラリに基づく代替機能列挙は問題ありません。
  • 2018 年現在、Chrome/ium は標準の配列組み込みメソッドをインライン化しています。
  1. 2019 年より前のブラウザーを対象とする場合は、MutationObserver コールバック内のような遅い ES2015 ループを使用しないでください。ただし、結果のコードがクラシックループfor (let v of something)と同じくらい速く実行されるようにトランスパイルする場合は除きます。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;
    }
    

質問に戻る:

非常に特定のコンテナを監視して、それに追加されているものがある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});
于 2016-09-05T14:04:23.123 に答える