アクセッサメソッド定義メソッド
ECMAScript ではメソッドの呼出しとプロパティの参照は透過的ではありません.
account.message() // メソッドの呼出し account.message // プロパティの参照
多態したい場合にはメソッド呼出しに統一するしかないので単純なプロパティへのアクセスもアクセサメソッドを定義してそれらを経由して行う必要があります.
しかし毎回以下のようなアクセサメソッドを定義するのは面倒です.
account.setMessage = function(message){ this.message = message } account.getMessage = function(){ return this.message; }
Ruby には Module#attr() というアクセサメソッドを定義するメソッドがあるので同じように Object.prototype に attr() を定義してしまいましょう.
Object.prototype.attr = function(name) { this.attr_reader(name); this.attr_writer(name); } Object.prototype.attr_reader = function(name) { var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1) var readerName = "get" + capitalizedName; this[readerName] = function(){ return this[name]; } } Object.prototype.attr_writer = function(name) { var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1) var writerName = "set" + capitalizedName; this[writerName] = function(newVaue){ this[name] = newVaue; } }
使い方は以下の通りです.*1
var obj = new Object(); obj.attr("message"); obj.setMessage("Hello, World!"); alert("obj.getMessage() : " + obj.getMessage()) var child = obj.clone() alert("child.getMessage() : " + child.getMessage()) // 継承されている child.setMessage("Bye!") alert("child.getMessage() : " + child.getMessage()) alert("obj.getMessage() : " + obj.getMessage()) // 親オブジェクトに影響しない
とりあえず問題なく動きます.しかしこの書き方だと定義されたアクセサメソッド実行時のスコープチェーンから attr() の実行時の Activation オブジェクトを参照することになります.これががちょっと気に入らないので以下のように変更しました.
Object.prototype.attr_reader = function(name) { var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1) var readerName = "get" + capitalizedName; this[readerName] = new Function('return this["' + name + '"]') } Object.prototype.attr_writer = function(name) { var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1) var writerName = "set" + capitalizedName; this[writerName] = new Function('newValue', 'this["' + name + '"] = newValue') }
Function コンストラクタを利用して作った Function オブジェクトはその実行時のスコープチェーンに Global オブジェクトのみを含むスコープを持つってのを利用しています.
ところで,attr() は Function オブジェクトをを生成してプロパティに代入しますが引数が同じならばまったく同じ処理の Function オブジェクトを生成することになります.同じ内容の Function オブジェクトを生成するのはいかにも無駄っぽいのでキャッシングしてみます.
Object.prototype.attr_reader = function(name) { var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1) var readerName = "get" + capitalizedName; var reader = arguments.callee.cache[name] if (!reader) { reader = new Function('return this["' + name + '"]') arguments.callee.cache[name] = reader; } this[readerName] = reader } Object.prototype.attr_reader.cache = new Object() Object.prototype.attr_writer = function(name) { var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1) var writerName = "set" + capitalizedName; var writer = arguments.callee.cache[name] if (!writer) { writer = new Function('newValue', 'this["' + name + '"] = newValue') arguments.callee.cache[name] = writer; } this[writerName] = writer; } Object.prototype.attr_writer.cache = new Object()
attr_reader() や attr_writer() の Function オブジェクト自体のプロパティにキャッシュ用のオブジェクトを作って作成したアクセサメソッドとなる Function オブジェクトを格納します.関数自体もオブジェクトだからこんなことが簡単にできるんですね.
ここまで書いてからこの処理で本当にキャッシュなんて必要なんだろうか,という疑問が浮びました.オブジェクトが GC により開放された後もアクセサの Function オブジェクトは開放されずに残るのでむしろ無駄なんじゃないだろうか.ってことで最後のは却下しました.
このメソッドはもう少しいじれそうな予感.
*1:http://d.hatena.ne.jp/sa-y/20060304#1141499154 で定義した Object#clone() を使用しています.