[解決済み] DOM全体のノードを検出するMutationObserverの性能
質問
私は
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のボディ全体を監視します。
var container = document.querySelector('body');
私が自分用に設定した基本的な例では、それが動作することを知っています... しかし、これを行うことは推奨されないのでしょうか?これはパフォーマンスの低下につながるのでしょうか? そしてもしそうなら、どのようにそのパフォーマンスの問題を検出し、測定するのでしょうか?
私は、すべての
MutationObserver
の例が対象となるコンテナについて非常に具体的であるのは、そのためかもしれないと思いました......が、よくわかりません。
どのように解決するのですか?
この答えは、主に大きく、複雑なページに適用されます。
ページのロード/レンダリングの前にアタッチされた場合、最適化されていない MutationObserver コールバックは、ページが大きくて複雑な場合、ページのロード時間に数秒を追加できます(たとえば、5 秒から 7 秒) ( 1 , 2 ). コールバックはマイクロタスクとして実行され、DOM のさらなる処理をブロックし、複雑なページでは 1 秒間に数百回から数千回実行される可能性があります。ほとんどの例や既存のライブラリは、このようなシナリオを考慮しておらず、見栄えが良く、使いやすいですが、潜在的に遅い JS コードを提供しています。
-
常に devtools プロファイラ を使用し、オブザーバコールバックがページロード中に消費する CPU 時間全体の 1% 未満になるようにします。
-
をトリガーしないようにしましょう。 強制的な同期レイアウト offsetTopや類似のプロパティにアクセスすることで
-
jQueryのような複雑なDOMフレームワーク/ライブラリの使用は避け、ネイティブDOMのものを好む
-
属性を観察するときは
attributeFilter: ['attr1', 'attr2']
オプションで.observe()
. -
可能な限り、非再帰的に直接の親を観察する (
subtree: false
).
例えば、親要素を待つために、観察することは意味がありますdocument
を再帰的に観測し、成功したらオブザーバを切り離し、このコンテナ要素に新しい非再帰的なオブザーバをアタッチします。 -
で一つの要素だけを待つときは
id
属性がある場合は、非常に高速なgetElementById
ではなく を列挙するのではなくmutations
の配列を列挙します (数千のエントリーがあるかもしれません)。 例 . -
目的の要素がページ上で比較的少ない場合(例えば
iframe
またはobject
によって返されるライブ HTMLCollection を使用します。getElementsByTagName
とgetElementsByClassName
を追加し、それらをすべて再確認します。 ではなく を列挙するのではなくmutations
を列挙するのではなく、例えば100以上の要素がある場合は -
の使用は避ける。
querySelector
の使用は避け、特に極端に遅いquerySelectorAll
. -
もし
querySelectorAll
が MutationObserver のコールバック内で絶対に避けられない場合は、最初にquerySelector
をチェックし、成功したらquerySelectorAll
. 平均して、このようなコンボはかなり速くなります。 -
2018年以前のChrome/iumをターゲットにしている場合、コールバックを必要とするforEach、filterなどの組み込みのArrayメソッドを使用しないでください。
for (var i=0 ....)
ループと比較して常に高価であり(10~100 倍遅い)、MutationObserver コールバックは複雑な最新ページで数千のノードを報告することがあります。
- lodashまたは同様の高速なライブラリに支えられた代替の関数列挙は、古いブラウザでも大丈夫です。
- 2018年現在、Chrome/iumは標準的な配列の組み込みメソッドをインライン化しています。
-
2019年以前のブラウザをターゲットにしている場合は は使用しないでください。 のように
for (let v of something)
のようなループを MutationObserver コールバックの内部で行う場合、トランスパイルしない限り、結果として得られるコードは古典的なfor
ループのように高速に実行されます。 -
もし目的がページの見え方を変えることで、追加される要素がページの見える部分の外側にあることを伝える信頼できる速い方法があるならば、オブザーバを切断し、ページ全体の再チェックと再処理をスケジュールするために、次のようにします。
setTimeout(fn, 0)
を介してページ全体の再チェックと再処理をスケジュールします。これは、解析/レイアウトのアクティビティの最初のバーストが終了し、エンジンが呼吸できるようになったときに実行されます。その後、たとえば requestAnimationFrame を使用して、チャンク内のページを目立たないように処理することができます。 -
処理が複雑であったり、多くの時間を要する場合、非常に長いペイントフレームや無反応/ジャンクにつながる可能性があるので、このような場合は デバウンス を使用するか、または同様の手法 (たとえば、外部配列に変異を蓄積し、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});
関連
-
[解決済み] 要素外でのクリックを検出するにはどうすればよいですか?
-
[解決済み] モバイル端末の検出にはどのような方法がありますか?
-
[解決済み] どのDOM要素にフォーカスがあるかを調べるには?
-
[解決済み] DOM要素が現在のビューポートで表示されているかどうかを確認するにはどうすればよいですか?
-
[解決済み] JavaScript で DOM ノードのすべての子要素を削除する
-
[解決済み] Safari、Chrome、IE、Firefox、Operaのブラウザを検出する方法は?
-
[解決済み] JavaScriptが無効になっているかどうかを確認する方法を教えてください。
-
[解決済み】DOMの変更を検出する
-
[解決済み] 各オブジェクトに?重複
-
[解決済み] Node.jsのES6クラスをrequireで作る
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] JavaScript / jQuery の DOM チェンジリスナーはありますか?
-
[解決済み] 配列からオブジェクトを生成する
-
[解決済み] チェックボックスが選択されているかどうかを確認するjQuery
-
[解決済み] URL/アドレスバーからJavascriptの関数を呼び出す
-
[解決済み] 文字列が空白であるかどうかをチェックする
-
[解決済み] JSXとLoadshを使用して、ある要素をn回繰り返す方法
-
[解決済み] Reactコンポーネントでthis.setStateを複数回使用するとどうなりますか?
-
[解決済み] 無効になっている入力フィールドの値を送信する
-
[解決済み] サブドメインにまたがってlocalStorageを使用する
-
[解決済み] Javascriptで動的に命名されたメソッドを呼び出すにはどうすればよいですか?