Edited at

Object.defineProperty 関数で Observable なオブジェクトを作る

More than 3 years have passed since last update.

JavaScript の Object.defineProperty 関数 (および Object.defineProperties 関数) は、オブジェクトにプロパティを動的に定義します。

この関数の引数 descriptor の get 属性と set 属性に、プロパティへのアクセサーとなる関数を指定できるわけですが、ここに任意の処理を追加できます。

この仕組みを利用して、Observable (監視可能) なオブジェクトを作ることができます。

ここでは、Observable なオブジェクトとは、プロパティ値の変更を通知するオブジェクトを指すこととします。

jQuery を活用して、次のように実装できます。


JavaScript

var obj = {};

Object.defineProperty(obj, "prop1", createDescriptor("prop1"));

function createDescriptor(name) {
var value;
return {
get: function () {
return value;
},
set: function (v) {
if (value === v) return;
value = v;
$(this).trigger("propertychange", [name, v]);
},
enumerable: true,
configurable: true
};
}

obj.prop1 = "Original";
$(obj).on("propertychange", function (e, name, value) {
alert(name + ": " + value);
});
obj.prop1 = "New";


通知の発行と受信には、それぞれ jQuery の trigger メソッドon メソッドを利用しています。

これで、prop1 プロパティの値が変更されると、その通知を受け取ることができるようになりました。

最後の行が実行されると、ダイアログが表示されます。

これを汎用化するために、コンストラクターにしてみます。


Observable.js

var Observable = (function () {

function Observable(obj) {
if (obj == null) return;
for (var p in obj) {
Object.defineProperty(this, p, createDescriptor(p, obj[p]));
}
}

function createDescriptor(name, value0) {
var value = value0;
return {
get: function () {
return value;
},
set: function (v) {
if (value === v) return;
value = v;
$(this).trigger("propertychange", [name, v]);
},
enumerable: true,
configurable: true
};
}
return Observable;
})();


これで、Plain なオブジェクトを Observable なオブジェクトに変換することができます。


Observable.jsのテスト

var obj = { id: 123, name: "Original" };

var observable = new Observable(obj);

$(observable).on("propertychange", function (e, name, value) {
alert(name + ": " + value);
});
observable.id = 456;
observable.name = "New";


このようなクラスを何に利用できるかというと、例えば Model と View を同期させる仕組みを作ることができます。

というわけで、このクラスとテンプレート エンジンを組み合わせた簡単なサンプルを作ってみました。

Data Binding Sample

Data Binding Sample

ここで使われているテンプレート エンジンについては詳しく説明しませんが、jQuery Templates のようなテンプレート エンジンを自作したものです。

<div class="times"> の部分が appModel というデータにバインドされていて、バインド時に appModel は Observable 型になります。

1 秒ごとにタイマーで Model を更新すると、それが自動的に View まで伝播します。

記述量が少なく、ロジックを追いやすい JavaScript で HTML アプリケーションを構築することができます。

ただし、Object.defineProperty 関数は IE8 以下で動作しません。

今後 Windows XP のサポートが終了し、jQuery 2.x が主流になってくる頃には、各 MVx フレームワークでもこういった手法が採り入れられていくことが期待されます。


バージョン情報

Internet Explorer 9 以降、その他の主要なブラウザー

jQuery 1.7 以降


参照

Object.defineProperty 関数 (JavaScript) (MSDN)

defineProperty (MDN)

ECMA-262 5th edition で導入された Object.defineProperty を使い、属性を指定してプロパティを定義する