MutationObserverはDOM要素の変化を監視するために追加されたJavaScriptのAPIです。
要素の変化とか追加・削除まで感知できるとても便利なAPIなんですが、1つ問題があります。
それはコールバック関数内で要素を変更したときに無限ループが発生してしまうこと
なので、ここではMutationObserver内で無限ループが発生してしまった時の対処法について紹介します。
MutationObserverとは?いつ無限ループが起こる?
MutationObserverというのは具体的に次のようなDOM変化を感知するためのAPIの名前
- 属性値・CSS値の変化
- 要素の追加・削除
- 要素内のテキストの変更
- その他様々な変化・・・
今まで普通のイベントでは感知できなかった要素変化が感知できる便利なAPIです。
この詳しい使い方については次で詳しくまとめてあります。
ただこのMutationObserverで気を付けなければいけないことが1つあって、
それが変化時に呼ばれるコールバック関数で要素を変更したとき無限ループが発生してしまうということ
例えば次のようなコードがその例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** MutationObserverの作成 */ var observer = new MutationObserver(function(record){ /** DOMの変化が起こった時の処理 */ $('#example').text('Text is changed in MuationObserver.'); }); /** idが"example"のdiv要素の変化を監視 */ observer.observe( document.getElementById('example'), { attributes: true, childList: true, characterData: true } ); |
これを実際に動かしてみるとどうなるかというと想像通り無限ループしてしまいます。
上のコードではMutationObserverのコールバック関数で要素の中身を変更しているので
- 要素のテキストが変更される
- コールバック関数が呼ばれる
- その中でさらにテキストを変更
- コールバック関数が呼ばれる
- その中でさらに(以下略・・・
という風に無限にコールバック関数が呼び出されてしまうのがループの原因です。
MutationObserverは純粋に要素変化を監視するだけなら特に問題はありません。
ただコールバック内で監視対象の要素にあれこれしてしまうと無限ループが発生してしまうんですよね。
MutationObserverの無限ループの対処法
ではその無限ループにはどう対処すればいいかというとdisconnect関数を使います。
これはMDNリファレンスによると次のような関数として説明されていました。
MutationObserver
インスタンスを対象ノードから解除します。同じインスタンスでobserve()
メソッドを再度呼び出すまで、オブザーバのコールバック関数は実行されません。
監視対象のノードを一時的に監視から外す関数のようです。
なのでこの関数とobserve関数を使って次のようにループ対策します。
- まずobserve関数で監視対象のノードを監視
- コールバック関数が呼ばれたらdisconnect
- コールバック関数終了時に再びobserver
こうすれば絶対にループが起こらないはずです。
では実際に例を使って説明しましょう。
まず監視対象の要素として次のような要素を作ったとします。
1 2 |
<div id="example">Example</div> <button id="change_bg_btn">Change background</button> |
div要素 ( id="example" ) とボタン要素 ( id="change_bg_btn" ) の2つです。
そしてボタン要素が押されたらdiv要素の背景色を変えることにします。
そしてその変化をMutationObserverで監視して、変化があったらdiv要素のテキストを変更
というような処理をしているのが次のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
$(function(){ /** 監視対象の要素オブジェクト */ const elem = document.getElementById('example'); /** 監視時のオプション */ const config = { attributes: true, childList: true, characterData: true }; var observer = new MutationObserver(function(record){ /** DOM変化の監視を一時停止 */ observer.disconnect(); $('#example').text('Background color changed'); /** DOM変化の監視を再開 */ observer.observe(elem, config); }); observer.observe(elem, config); /** ボタンが押されたらdiv要素の背景色変更 */ $('#change_bg_btn').on('click', function(){ $('#example').css('background', 'lightgray'); }); }); |
変なコードかもしれませんが、いい例が思いつかなかっただけなので気にしないで下さい。笑
それでこのコードで無限ループを阻止しているのが次の部分
1 2 3 4 5 6 7 8 9 |
var observer = new MutationObserver(function(record){ /** DOM変化の監視を一時停止 */ observer.disconnect(); $('#example').text('Background color changed'); /** DOM変化の監視を再開 */ observer.observe(elem, config); }); |
まずMutationObserverのコールバックが呼ばれたらdisconnect関数で監視を中断しています。
そしてコールバック関数の処理が一通り終わったら、またobserve関数で監視を再開してるだけです。
たったこれだけですが無限ループが起こるのを阻止できます。
コールバック内でフラグを使うという方法も試してみましたが、disconnect関数を使えば間違いなく監視を止めることができるのでこっちの方が確実ですね。
ここまでのまとめ
ということで今まで説明したことをまとめると次の通り
- MutationObserverで無限ループが起こる原因
コールバック関数内で監視対象の要素(ノード)を変更しているから
- その無限ループを阻止するための対処法
コールバックが呼ばれたら即disconnect、コールバックから抜けるときに再びobserve
MutationObserverは便利ですが、無限ループが起こると中々厄介です。
そうならないためにもループ対策はしっかりとしておいた方が良いでしょう。
ではでは(・ω・)ノバイバイ