JavaScript
ECMAScript6
ECMAScript2015

JavascriptでスマートにObservableする 改2

前回

https://qiita.com/angeart/items/29979f67d7a511bbec0e

まだスマートにできる

お前に足りないものは、それは!
情熱・思想・理念・頭脳・気品・優雅さ・勤勉さ!そしてなによりもォォォオオオオッ!!
スマートさが足りない!!

改善点

プロパティレベルでのイベントハンドラ分離を行った
jQuery依存削除

実装

class Observable {
  constructor() {
   this.event = {};
    return new Proxy(this, {
      set: function(obj, prop, value) {
        if (obj[prop] === value) return true;
        obj[prop] = value;
        if(obj.event[prop + '_onchanged']) {
            obj.event[prop + '_onchanged'].bind(obj)();
        }
        return true;
      }
    });
  }
}

class Test extends Observable {
  constructor() {
    super();
    this.event.hoge_onchanged = () => {console.log('changed : ' + this.hoge)};
  }
}

var tester = new Test();

tester.hoge = "hoge";
tester.hoge = "hoge";
tester.hoge = "fuga";

if (tester instanceof Test) {
  console.log("ok");
}

実行結果

changed : hoge
changed : fuga
ok

解説

まともに解説していなかったので解説します。

変更の監視

    return new Proxy(this, {
      set: function(obj, prop, value) {
        if (obj[prop] === value) return;
        obj[prop] = value;
        if(obj.event[prop + '_onchanged']) {
            obj.event[prop + '_onchanged'].bind(obj)();
        }
      }
    });

まず、この実装の根本ともいえるオブジェクトに対する変数の監視をする部分はProxyクラスを用いて実現しています。
リンク先のMDNをご覧になると分かると思いますが、このProxyクラスはあるオブジェクトに対する操作をラップできるクラスとなっています。どの操作をラップできるかについてはProxy Handlerの項目を見るとわかります。
このクラスを用いてラップすることによって実装者による独自の動作をさせることができますが、注意すべき点として、本来の動作をさせつつ何かをしたい場合はトラップ関数に本来の動作を行う実装を書かなければいけません

通知の発行/受け取り

いろいろ悩んだのですが以下のように落ち着きました

発行

function(obj, prop, value) {
  if (obj[prop] === value) return;
  obj[prop] = value;
  if(obj.event[prop + '_onchanged']) {
    obj.event[prop + '_onchanged'].bind(obj)();
  }
}

objにはProxyクラスのコンストラクタ第一引数に指定したオブジェクトが渡されます。
propには変更があったプロパティが渡されます。
valueは変更後の値です。
ですので
obj[prop] = value
とすることで元々のsetの役目を果たすことができます。

受け取り

this.event.hoge_onchanged = () => {console.log('changed : ' + this.hoge)};

次にeventオブジェクトにハンドラが存在する場合のであればイベントを発行し、this.event.<prop>+_onchangedで受け取っています。
ここはobj直属の関数を呼び出してもよかったのですが、objに大量のイベントハンドラが追加されるのは気持ちが悪いのでObservableクラスにeventオブジェクトを作成し、そこでイベントハンドラを記述するようにしています。

所感

結局TestObservableを継承していますが、役目としてはObserver,Observable両方の役目を果たしています。
これ以上スマートにする方法が見当たらないので一旦完成かなと思います。
もし、何か指摘点等あればコメントいただけると幸いです。

repl.it

https://repl.it/@Angeart/Observable-on-Javascript-3

追記

タイトルがスマートじゃない