LoginSignup
8
5

More than 5 years have passed since last update.

JSで値変更を検知したい-2(配列・defineProperty編)

Last updated at Posted at 2019-02-25

はじめに

実装方針

  • 配列監視のはじめのアプローチとしては「前回使った 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 を使った方法ではなく、別のアプローチで考える必要があります。が、詳細は 次回 へ・・・
8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5