Heaven's Kitchen

○ JavaScriptでカプセル化

またしても野望の会ブログに反応してみる。

http://www.yabooo.org/archives/53

JavaScriptではC++やJavaと違ってprivateなインスタンス変数や,メソッドが定義できないのでなんとかしましょうという話。

同じようなことがPerlでもやられていて,Perl Hacksでは裏返しオブジェクトというのが紹介されています。上記のエントリーと同じ発想で,クロージャを使うわけですがオブジェクトの保存場所が微妙に違います。JavaScriptでやるとこんな感じ?

var Class = {
    'create': function (tmpl) {
	var repos = [];
	var klass = function () {
	    this._id = repos.length;
	    repos.push({});
	    tmpl.initialize.apply(repos[this._id], arguments);
	};
	for (var i in tmpl) {
	    if (i == 'initialize') continue;
	    if (typeof tmpl[i] == 'function')
		klass.prototype[i] = function () {
		    return tmpl[i].apply(repos[this._id], arguments);
		};
	}
	klass.extend = function (obj) {
	    for (var i in obj) {
		klass.prototype[i] = function () {
		    return obj[i].apply(repos[this._id], arguments);
		};
	    }
	};
	return klass;
    }
};

var Foo = Class.create({
	'initialize' : function () {
	    this.x = 100;
	},
	'getX' : function () {
	    return this.x;
	}
    });

var foo = new Foo;

print(foo.getX()); // 100
print(foo.x);      // undefined

メソッドは全部パブリックだし,いろいろ手抜きだけどとりあえず隠蔽はできてます。クラスのコンストラクタそのものをクロージャにしてしまうとこがポイントですね。

メソッドはprototype経由で呼び出されるので,オブジェクトをnewする度に発生する,メモリ消費のオーバヘッドもありません(たぶん

さらに,あとからメソッドを追加したくなった場合は

Foo.extend({
	'printX' : function () {
	    print(this.x);
	}
    });

foo.printX(); // 100

とかできて,うれしい気持ち…。

Comment Thanks

(no subject)

ちょっと前の、「JavaScriptで遅延評価」のエントリのソースは再帰っぽいところで最後までよく読めなかったけど、今回は読めた!\(^o^)/ヤター

こういうの考えられるの、すごいね~。

(no subject)

反応サンクス!

Inside out Objectは考えたんだけど、

Publicメソッドを内部から呼ぶいい方法がみつからなかったので

かけなかったんだー。

あと今調べてるんだけど、

これFirefoxだと動作しないっぽい。

getX()がundefinedになるのと

setXをつくっても反映されない><

理由わかったらupするね。

今度のもんじゃ会に遊びにおいでよよ。

もし時間あればだけど。

(no subject)

>masutaro

読んでくれてありがとー。

でもいろいろ穴があったみたいです(汗

> ひろきの

こちらこそ,ご指摘どうもですm(_ _)m

たしかにsetXとか付けると,ブラウザで動かない><

publicメソッドも内部からは呼べない><

いろいろダメダメ…orz

もうちょっといろいろやってみます。

もんじゃ会はたぶん参加しまーす。よろしく〜。

(no subject)

というわけで修正版。

var Class = {
    'create': function (proto) {
	var repos = [];

	var Proto = function () {};
	Proto.prototype = proto;

	var klass = function () {
	    this._id = repos.length;
	    repos.push(new Proto);
	    proto.initialize.apply(repos[this._id], arguments);
	};

	for (var i in proto) {
	    if (i == 'initialize') continue;
	    if (typeof proto[i] == 'function') {
		klass.prototype[i] = function (ix) {
		    return function () {
			return proto[ix].apply(repos[this._id], arguments);
		    };
		}(i);
	    }
	}

	klass.extend = function (obj) {
	    for (var i in obj) {
		if (typeof obj[i] == 'function') {
		    klass.prototype[i] = function (ix) {
			return function () {
			    return obj[ix].apply(repos[this._id], arguments);
			};
		    }(i);
		    Proto.prototype[i] = obj[i];
		}
	    }
	};

	return klass;
    }
};

function main() {
    var Foo = Class.create({
	    'initialize' : function (x) {
		this.x = x;
	    },
	    'getX' : function () {
		return this.x;
	    },
	    'setX' : function (x) {
		this.x = x;
	    },
	    'foo' : function () {
		print(this.getX());
	    }
	});

    var foo = new Foo(100);
    print(foo.getX()); // 100
    print(foo.x);      // undefined
    foo.setX(200);
    foo.foo();         // 200
}

window.onload = main;

原因はループ変数のiだったような気がする。

Publicメソッドを内部から呼ぶってこれで合ってるかな…。

ていうかこの方法の一番の問題点は_idを隠蔽できていないことのような気もする。

(no subject)

インサイドアウトオブジェクトでIDを隠蔽する方法も

考えてあって、後日まとめて野望ブログにのせようかと

おもっていたんだけど、ネタなので実験用の関数さらします。

たぶん、何をしようとしてるのかわかるはず。

this._idじゃなくてthisからidを取り出す比較的効率の

よさそうな方法を考えてる。

function getPosFunc(array){

var text=[];

var length=array.length;

text.push("var f=function(obj){")

text.push("switch(obj){");

for(var i=0;i

text.push("case array["+i+"]:return "+i+";");

}

text.push("}};");

eval(text.join(""));

return f;

}

var K=function(){this.x=10;}

var A=[];

for(var i=0;i<100;i++){

 A.push(new K);

}

var getPos=getPosFunc(A);

console.log(getPos(A[11]));

(no subject)

なるほど!!

JavaScriptじゃobject_idみたいなことできないから,無理かなぁと思ってたけど,こういう手があったか〜。

というかこれを見た後でamachangの

すべてのオブジェクトをキーにできるハッシュの様なもの

っていうエントリーを思い出した。これでも同じことができそうだけど,どっちが効率いいかな…。いずれにせよthisからidを取り出すのって線形探索するはめになって,たくさんnewされるクラスには向かないような気もする… 。

# あ,ちなみにここのコメントははてな記法を処理するようになってます…。どこにも明記してなくてごめんなさい(泣

(no subject)

>はてな記法

了解しました。

この方法の場合newのたびにevalのコストはかかるけど

メソッド呼び出し時にはすでにswitchなんで線形検索じゃなくなる

はずっていうところなんだけど自信はない。

つくづくポインタのない言語はこれだから困る。

(no subject)

switchって実行時に上から順番に各caseと===で比較していくから線形探索だと思ってたんだけど違うの?C言語だと分岐条件とこには整数しか書けないから,最適化でなんとかなりそうだけどJavaScriptは,実行時にcaseの式の評価もしていくから無理なんじゃと思ってた…。いや,俺も自信ないけど,すごく真相が知りたくなってきた。

<< 前の日記 "世界のナベアツ問題"(2008-02-03)

>> 次の日記 "Perlの罠1"(2008-02-15)

Valid XHTML 1.0! Valid CSS!