はじめに
- 前回「JSで値変更を検知したい-1(オブジェクト・defineProperty編)」の続きです。
- 今回は オブジェクトのプロパティに配列が含まれていた場合の対応を検討してみます。
- 用意するもの、準備は前回の記事と同じです。
実装方針
- 配列監視のはじめのアプローチとしては「前回使った
Object.defineProperty
が使えないか」という方向から考えてみます。 - ただし、配列の全要素に対し、1つ1つ ・・・というのは件数が増えたらヤバそうです。。。
- ここは 配列を操作するメソッド をカスタマイズできないか検討してみましょう。
- 参考
実装1:push() を自分で定義する
- おそらく最もよく使うであろう 値を末尾に追加する
push()
メソッドの動作をカスタマイズしてみます。
コード
/**
* 配列の変更を監視します
* @param {Array} arr 監視対象の配列
* @param {String} propName 監視対象のプロパティ名
* @param {function(Object, Object)} func 値が変更された際に実行する関数
*/
function watchArray(arr, func) {
Object.defineProperty(arr, 'push', {
value: function () {
const oldArray = [...this];
// push() の動作
let n = this.length;
for (let i = 0; i < arguments.length; i++ , n++) {
this[n] = arguments[i];
}
// 追加後に任意の処理を呼ぶ
func(oldArray, this);
return n;
}
});
}
// 適当なオブジェクトを定義
const obj = {
name: '太郎',
pets: ['dog']
};
// 変更時に実行したい関数を定義
function onChange(v1, v2) {
console.log(v1);
console.log(' =>', v2);
console.log('');
};
// 監視(今回は配列のみ監視対象とする)
watchArray(obj.pets, onChange);
// 実際に変更してみる(値を追加)
obj.pets.push('mouse');
obj.pets.push('rabbit');
実行結果
[ 'dog' ]
=> [ 'dog', 'mouse' ]
[ 'dog', 'mouse' ]
=> [ 'dog', 'mouse', 'rabbit' ]
- 実行すると配列に値が
push
された際の動作をカスタマイズでき、変更を検知できていることがわかります。
ポイント
-
defineProperty
で 配列オブジェクトを指定、メソッド名'push'
を指定します -
defineProperty
で指定しているデスクリプタ のvalue
というプロパティで動作を定義します(push
という動作のvalue
… なんだか変な感じですが) -
value
: アロー演算子でなくfunction
キーワードを指定しています- これにより
this
のスコープが 与えられた 配列インスタンス (arr
)となります。
- これにより
-
const oldArray = [...this];
としているのは、単にconst oldArray = this;
とすると配列への参照がoldArray
変数に格納されてしまいます。- 後の処理で配列に変更を加えると
oldArray
にも影響があります(単に参照しているだけなので) - スプレッド演算子(...)を使い、配列の中身を取り出した後、再度ブラケットで配列として再定義します。
- ただしこの方法では、オブジェクト配列の場合には通用しません。(ディープコピーまではできません。本記事のサンプルではオブジェクト配列でないため、うまく動作します)
- 後の処理で配列に変更を加えると
-
for
文 で自身(this = 配列インスタンス)について 配列を自力で操作しています(後述・問題点)
実装2:他のメソッドをぜんぶカスタマイズする(絶句)
- 実装1で
push
による変更検知が無事にできました! - 配列の監視を実現するためには 残り全部 の Arrayクラス の 副作用のありそうな メソッド をカスタマイズすれば良さそうですね!!
- ・
- ・・
- ・・・
- 実装1では 既存のメソッドの動作を自分で実装 していました。
- Arrayクラスのメソッドぜんぶ・・・?
- 次世代の ES がやってきて、メソッド増えたり仕様変わったら・・・?
- そもそもこの自作の動作、本当に正しいの・・・?
問題点
Arrayクラスメソッドの動作を自分で実装しなければならない
- 確かに期待した動作は得られましたが、リスクが大きいですね。。。
-
defineProperty
を使った方法ではなく、別のアプローチで考える必要があります。が、詳細は 次回 へ・・・