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);
というような記述に比べれば少しはマシだけど、もっと短くできないもんかなー。