ECMAScript 5th 的 LazyGetter
ECMAScript 5th から正式に getter/setter が作れるようになったわけだが、これはMozilla の JavaScript である SpiderMonkey では前々から実装されていた。
{ get foo() { return ...; } }
{ set foo(val) { return ...; } }
に加え、
Object.prototype.__defineGetter__
Object.prototype.__defineSetter__
が使えたわけだ。
で、これを使用したハックとして、以下のようなものがある。
function Foo () { this.__defineGetter__("bar", function(){ var val = "BAR"; delete this.bar; return this.bar = val; }); } var f = new Foo(); Object.getOwnPropertyDescriptor(f, "bar"); /* => { configurable: true, enumerable: true, get: function () { var val = "BAR"; delete this.bar; return this.bar = val; }, set: undefined } */ f.bar; // "BAR"; Object.getOwnPropertyDescriptor(f, "bar"); /* => { configurable: true, enumerable: true, value: "BAR" } */
これは、プロパティbarが[[Get]]されたときに、
- 値を取得
- getterを削除
- 同名のプロパティを設定
するというもの。遅延読み込みとして使える技だ。
が、しかし、上記コードでコンストラクタ上で定義したのは意味がある。
function Foo(){ } Foo.prototype = { get bar() { var val = "BAR"; delete this.bar; return this.bar = val; } };
ということはできない。何故なら、
- barプロパティはthisのOwnPropertyにあるのではなく、プロトタイプ上にあるため、deleteできない
this.bar = val
の代入演算子はプロトタイプをさかのぼってチェックするため- 左辺値への参照を探り、それがAccessorDescriptorであり、[[Set]]がundefinedなので何もしない
- Strictモードであれば、ここで例外を投げる
そんなわけで、コンストラクタ上にgetterを定義しなくてはならず、なんだか不恰好だし、コンストラクタが呼ばれるたびにgetterを定義するわけで非効率だなという印象を持っていた。
はい、前置き終わり。(長い前置きだ)
というわけで、ECMAScript 5th ですよ。上記問題点は代入演算子(=)がプロトタイプを遡る点(これはFirefox4 と Chrome, Safari の ES5 実装でも書いた)。
ならば、プロトタイプを遡らないObject.defineProperty
を使えばいいじゃない。
function Foo () { } Foo.prototype = { get bar() { var val = "BAR"; Object.defineProperty(this, "bar", { configurable: true, enumerable: true, writable: true, value: val }); return val; } };
ECMAScript 5th、すばらしいですね。