superなメソッドの呼び出し

prototype.jsに限った話じゃないけど、クラスを作ってoverrideしたいことが結構ある。
でも実はメソッド単位でoverrideしたいということに気付いて、こんなテストケースを書こうとした。

	var classA = Class.create();
	classA.prototype = {
		initialize: function() { this.result = []; },
		foo: function() { this.result.push("fooA"); }
	}
	Function.overrideMethod( classA.prototype, "foo", 
		function() {
			super(this, arguments);
			this.result.push( "fooB" );
		}
	);
	Function.overrideMethod( classA.prototype, "foo", 
		function() {
			super(this, arguments);
			this.result.push( "fooC" );
		}
	);
	var objA = new classA();
	objA.foo();

これを実行したら、objAのresultが["fooA", "fooB", "fooC"]となっているようにしたい。
んで、問題は super(this, arguments) ってところ。これは僕が勝手にこういう風にしたいなーと思っただけで、全然動かない。これに代わる実装を作るっていう話です。


メソッド名に"super_"とか付けちまえばいいんじゃねーの?とか最初は思ってた。
Function.overrideMethodを呼び出すたびに、classA.prototypeが

{ foo: function(){...} }
{ foo: function(){...}, super_foo: function(){...} }
{ foo: function(){...}, super_foo: function(){...}, super_super_foo: function(){...} }

っていう風に増えてきゃいいじゃんって簡単に思ってたけど、それだと呼び出し時に困る。というのは、

function() {
	this.super_foo(arguments);
	this.result.push( "fooB" );
}

って書くと、"fooB"を出力するところでは、結果的に自分自身を呼び出し続ける破目になるから。
これをどうにかいい感じにしようと調べたら、arguments.calleeなるものを発見。こんな感じにしてみた。

	var classA = Class.create();
	classA.prototype = {
		initialize: function() { this.result = []; },
		foo: function() { this.result.push("fooA"); }
	}
	Function.overrideMethod( classA.prototype, "foo", 
		function() {
			arguments.callee.superApply(this, arguments);
			this.result.push( "fooB" );
		}
	);
	Function.overrideMethod( classA.prototype, "foo", 
		function() {
			arguments.callee.superApply(this, arguments);
			this.result.push( "fooC" );
		}
	);
	var objA = new classA();
	objA.foo();

arguments.calleeは現在実行している関数を指すそうです*1
で「現在実行している関数」が取得できれば、そいつのsuperなメソッドが分かれば呼び出せるので、実装はこんな感じに。

/**
 * overrideとsuperな実装呼び出し時に使う、superを表す文字列
 */
Function.superSigniture = "__super";

/**
 * メソッド単品をオーバーライドするためのFunctionの拡張
 */
Function.overrideMethod = function(object, name, f) {
	if (!object)
		throw new Error("object is undefined. name=" + name + " f=" + f);
	var src = object[name];
	if (src) 
		f[Function.superSigniture] = src;
	object[name] = f;
} 

/**
 * Function.overrideMethodでoverrideされたメソッドの
 * superな実装を呼び出す。もしsuperな実装がなければ何もしない。
 */
Function.prototype.superApply = function( object, args ) {
	var superMethod = this[Function.superSigniture];
	if (!superMethod)
		return void(0);
	return superMethod.apply(object, args);
}

まあこれで、僕としてはちょっと便利になったんだけど、いちいち

arguments.callee.superApply(this, arguments);

って書くのもどうだろう?prototype.jsを使うとよく書く

Event.observe(window, "load", xxxxx.bindAsEventListener(this), false);

というような記述に比べれば少しはマシだけど、もっと短くできないもんかなー。

*1:こういう属性持ってるから、argumentsのコンストラクタはArrayじゃないんですな。なるほど