自分用class定義ライブラリmyclass.jsを作ってみる(3)
親クラス($super)の参照方法を考える
前回のエントリ(こちら)で、親クラスのinitの実行を
this.$super.init();
のように記述できるようにしましたが、この部分に関し問題があることに気付きました。問題というのは、this.$super経由のメソッドの実行により、親クラスの定義が書き換えられてしまうというものです。以下に具体例を書きます。
var Aaa = MyClass.create({ init : function(a){ this.a = a||this.a; return this; }, putA : function(){ alert('a is '+ this.a); }, a : 'A' }); var Bbb = MyClass.create(Aaa,{ init : function(b){ this.$super.init(b) return this; } }); Aaa(123).putA(); //a is 123 Aaa().putA(); //a is A Bbb(456).putA(); //a is 456 Aaa().putA(); //a is 456 → 'a is A' でないとまずい!
Bbb(456)の実行でthis.$super.init(b)が行われ、Aaa.aの初期設定値が書き換わってしまってます。原因は$superの参照先が、メンバ定義として存在する親オブジェクトそのものなってしまっていたためです。(以下赤字部分相当)
ですので、以下のよう実体化したオブジェクトを$superの参照先にする必要があります。(全体のソースは前回のエントリを参照下さい。)
修正前
if(ext){ cns.prototype = MyClass.Util.getInstance(ext.prototype); cns.prototype.$super = ext.prototype; } MyClass.Util.extend(cns.prototype,member);
修正前
if(ext){ //1回目の実体化 var Super = MyClass.Util.getInstance(ext.prototype); MyClass.Util.resetDeepInstanceProperty(Super); //2回目の実体化 cns.prototype = MyClass.Util.getInstance(Super); //1回目に実体化したオブジェクトを$superとする cns.prototype.$super = Super; } //2回目に実体化したオブジェクトにメンバを追加する MyClass.Util.extend(cns.prototype,member);
修正後の処理では、実体化を2度やっており冗長に見えますが、拡張メソッドやプロパティが追加されてないオブジェクトを$superとする必要があるためです。(1度だけだとinitメソッドのように親子両方に存在するメンバは上書きされ、その上書きされたオブジェクトが$superとなってしまうため子オブジェクトから親オブジェクトのinitが参照できなくなってしまう。)
ややこしいので図にしてみました。
修正後
絵にしてみたら(自分の中では)すっきりしました。で、これで問題が解決したかと思ったのですが解決してませんでした。上記の修正で確かに親クラス定義の書き換えはされなくなりましたが、$superの初期値が書き換えられてしまうようになりました。以下が具体例です。
Aaa(123).putA(); //a is 123 (OK) Aaa().putA(); //a is A (OK) Bbb(456).putA(); //a is 456 (OK) Aaa().putA(); //a is A (OK) Bbb().putA(); //a is 456 (NG)→ 'a is A' でないとまずい!
これは$super(図の実体A')の生成処理をMyClass.createのクラス定義処理で行っていたためで、生成される実体毎にプロパティ値を保持するためには、MyClass.createの返却関数の中で、$superを生成する必要がありました。こうなってくるとMyClass.createの処理全体を見直す必要があったので、最初から作り直してみました。次のエントリ(こちら)へつづきます。