A-SKが綴る国境を超えたSTORY

貧乏エンジニアA-SKが綴るトヨタ86を改造するための稼ぐHowToブログ

【JavaScript】MutationObserverを使って、DOMのリアルタイム監視実装

f:id:eisukenakanishi:20170228122148j:plain

はじめに

このブログにて初めてエンジニアらしい記事を書きます。
今回個人的にコードを実装していく中で、DOMをリアルタイムで監視したいという要件が出てきました。
その際にsetTimeoutなどで、一定時間ごとに繰り返し監視するなどの方法もありますが、そちらは効率が悪かったり、負荷がかかるので・・・。
その代わりに今回紹介するObserverを使ってみたいと思います。

初期段階から詰まっていたのが、言葉の壁だったりします。英語はできても、カタカナになるとわからなくなることが多かったので、いちいちそちらも調べながら実装していました。
最初にオブジェクトというものに対して「ん??」となっておりました。
モノという意味であることはわかっていたのですが、モノ・・・ん??という状況でした。 実際にサンプルを見たらわかりやすくなってきました。

物理的あるいは抽象的な実体を、属性(データ)と操作(メソッド)の集合としてモデル化し、コンピュータ上に再現したもの。

こちらの文章を読んで、なるほど!となりました。
データは多分わかると思います、メソッドも下記で記述しておりますが操作するための命令文とでも思っておけばいいと思います。
それらをモデル化したモノがオブジェクト、という認識でいます。

コンストラクタ

コンストラクタとは、クラスからオブジェクトを作成した際に、自動的に実行されるメソッドのことで、メンバ変数の初期化などの主に行います。

MutationObserverというこちらのドキュメントが一番わかりやすいと思います。
ただ、残念ながら僕はここで何度かずっぷりとハマったので、そちらも併せて解説していきたいと思います。

まず冒頭に記載されている、

MutationObserver(
    function callback
);


こちらが基本的なObserverを生成するものになります。
上記がDOMの変更を検知するObserverを生成するものになります。

DOM の変更時に呼び出される関数です。この関数は第 1 引数に MutationRecord の配列を、第 2 引数に MutationObserver インスタンス自身を受け取ります。


しかし、これだけ見てもよくわからないので、細かく見ていきましょう。

インスタンスメソッド

インスタンスメソッドという言葉自体よくわからなかったのですが、いわゆるインスタンス(クラスを元にした実際の値としてのデータ)に対してメソッド(操作)を行うための型なのかな、と認識しました。
インスタンスとクラスについて、なかなか良くわからなかった自分がいたのですが、クラスが[名前、身長、体重]とある場合、インスタンスは[A-SK、178cm、79kg]のように捉えるとわかりやすいと思います。

今回のObserverというコンストラクタ(自動的に実行されるメソッド)に対しての命令文として、下記のものがあります。

observe(Node target, MutationObserverInit options);

disconnect();

takeRecords();

上記がMutationObserverの中に定義されているインスタンスメソッドと呼ばれるものです。

observe();

DOMの変更と検知したいNode(ノードとは、DOMでアクセス・変更できるブロック単位)を指定して、MutationObserverインスタンスと紐付けます。
なので、Node(HTML全文がNodeで作られています)の一部が変更された際に、この処理を走らせたい!という場合にこのMutationObserverが役に立つということですね。

observe(
  Node target,
  MutationObserverInit options
  );


ここで記述しているtargetという引数はDOMの変更を検知したいNodeを定義して入れてあげたりします。
optionsの部分には、オプションを指定して上げる必要があります。その中でも必須で入れなければいけないものがいくつかあります。

childList / attributes / characterDataに対してtrueを指定してあげる必要があります。
実際に記述すると、こんな感じです。

var config = { attributes: true, childList: true, characterData: true };


変数configに対して、上記のoptions群をtrueで指定してあげます。

disconnect();

このインスタンスメソッドは、インスタンス対象ノードから解除します。なので、基本的にずっと監視させたい場合これをtrueにしてしまうと、どっかで走らなくなります。
僕は上記のリンクのドキュメント通りに進めていたら、ここで詰まりました。つまり走らなくなったので、「なぜだ!?」というところから、このインスタンスメソッドをtrueにしているからだ!ということに気づく・・・。

対処方法としては、同じインスタンスでもう一度observe();メソッドを呼び出してあげないと、ObserverのCallback関数は実行されないので、ちょっと最初にやるにはややこしかったので、以下で紹介するサンプルコードのdisconnect();の一文は削除しました。

ただ、どこかで処理を止めたい時、または一度だけ走らせたいときなどはこれを指定してあげる必要があります。

takeRecords();

これはCallback関数へ渡されるMutationRecordのキューを空っぽにして、キューに入っていた値を返してくれます。

僕はあまり使わなかったですね。基本的な実装をする場合は使わないのかなーというイメージです。

使用例

では、実際にサンプルを見てみましょう。

// 対象とするノードを取得
const target = document.getElementById('some-id');

// オブザーバインスタンスを作成
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    console.log(mutation.type);
    });    
});

// オブザーバの設定
const config = { attributes: true, childList: true, characterData: true };

// 対象ノードとオブザーバの設定を渡す
observer.observe(target, config);

// 後ほど、監視を中止
observer.disconnect();


こちらのサンプルはES6に基づいて構築されています。ES6でなければ、

// 監視して変更があった場合に実行される
var hoge = function() {
//変更があった際に実行されるコード
};
// fugaの中身を監視
var fuga = document.querySelector('監視したいパーツを入れる');
// 必須パラメーター
var config = { attributes: true, childList: true, characterData: true };
// observerインスタンスを生成
var observers = new MutationObserver(home);
// fugaの処理とoptionsを渡す
observers.observe(fuga, config);


上記のように実装すると、動くと思われます。
ちなみに、この状況だとdisconnect();がないので、監視されているfugaが変わるたびに処理が走ります。

まとめ

プログラミングとか訳わからなーい!状況がしばらく続いていましたが、だいぶわかってきたので、一度ドキュメント化しました。
しかしながら、一度わかって実装すると、使い回しが出来る部分もたくさん出てくるので、ストックするといいですね。

以上になります。
間違っている、こここうした方がもっといいよ!とあったら、コメントお願い致します。

この書籍はかなり刺激になります。

パーフェクトJavaScript (PERFECT SERIES 4)

パーフェクトJavaScript (PERFECT SERIES 4)