JavaScript で C++ っぽいクラスを実装してみた
js で、C++ ライクなクラス
- コンストラクタ(init)とデストラクタ(fin)が使える
- 三階層まで継承できる(子<親<親親)
- uu.Class("B : A", {}) や
- uu.Class("C < B", {}) で簡単に継承できる
- 継承が不要なら uu.Class("C", {}) で素のクラスを定義できる
- 下位のクラスから上位のクラスのメソッドが呼べる
- this.superMethod(arguments.callee, "呼び出すメソッド名", var_args...) または
- this.superMethod(呼び出し元のメソッド名, "呼び出すメソッド名", var_args...)
- 全部 public
- 多重継承とか static メソッドとかは無し
を実装してみました
こんな感じ
uu.Class("A", { init: function Ainit() {}, // コンストラクタ fin: function Afin() {}, // デストラクタ hoge: function Ahoge() {}, huga: function Ahuga() {}, piyo: function Apiyo() {} }); uu.Class("B : A", { // B は A を継承 (C++ チックに) init: function Binit() {}, fin: function Bfin() {}, hoge: function Bhoge() {} // huga: function() {}, // piyo: function() {} }); uu.Class("C < B", { // C は B を継承 (Ruby チックに) init: function Cinit() {}, fin: function Cfin() {}, hoge: function Choge() {}, // huga: function() {}, piyo: function Cpiyo() { this.superMethod(arguments.callee, "piyo", 1, 2, 3); // Apiyo.call(this, 1, 2, 3) 相当 } }); // インスタンスの生成 var a = uu.factory("A", 1, 2); // new uu.Class.A(1, 2); でも同じ var b = uu.factory("B", 1, 2); // Binit(Ainit()) 相当 var c = uu.factory("C", 1, 2); // Cinit(Binit(Ainit())) 相当 c.hoge(); // Choge() を呼ぶ c.huga(); // Ahuga() を呼ぶ c.piyo(); // Cpiyo(Apiyo()) を呼ぶ // 明示的にインスタンスを破棄(全プロパティが null に設定される) c.fin(); // Afin(Bfin(Cfin())) 相当 c.piyo(); // エラー「そんな関数ありません」
実装
// uu.Class - create a generic class function uuclass(className, // @param String: "Class" // or "Class:SuperClass" // or "Class<SuperClass" proto) { // @param Hash(= void 0): prototype object var ary = className.split(/\s*[\x3a-\x40]\s*/), tmp, i, Class = ary[0], Super = ary[1] || ""; uuclass[Class] = function uuClass() { var lv3 = this, lv2 = lv3.superClass || 0, lv1 = lv2 ? lv2.superClass : 0; uuclassguid(lv3); lv3.msgbox || (lv3.msgbox = uunop); uu.msg.register(lv3); // constructor(lv1 -> lv2 -> lv3) lv1 && lv1.init && lv1.init.apply(lv3, arguments); lv2 && lv2.init && lv2.init.apply(lv3, arguments); lv3.init && lv3.init.apply(lv3, arguments); // destructor(~lv3 -> ~lv2 -> ~lv1) lv3["~fin"] = lv3.fin || uunop; lv3.fin && uuevattach(win, "unload", function() { lv3.fin && lv3.fin(); }); lv3.fin = function wrapper() { lv3["~fin"](); lv2 && lv2.fin && lv2.fin.call(lv3); lv1 && lv1.fin && lv1.fin.call(lv3); // destroy them all for (var i in lv3) { lv3[i] = null; } }; }; uuclass[Class].prototype = proto || {}; if (Super) { tmp = function() {}; tmp.prototype = uu.Class[Super].prototype; uuclass[Class].prototype = new tmp; for (i in proto) { uuclass[Class].prototype[i] = proto[i]; } uuclass[Class].prototype.constructor = uuclass[Class]; uuclass[Class].prototype.superClass = uu.Class[Super].prototype; uuclass[Class].prototype.superMethod = superMethod; } function superMethod(from, // @param Function: caller to // @param String: /* var_args */) { // @param Mix: args var obj = this.superClass; // recurtion guard if (from === obj[to] || superMethod.caller === obj[to]) { obj = obj.superClass; } return obj[to].apply(this, uu.ary(arguments).slice(2)); } }
補足
- 名前付きのメソッドを使っている場合は、 this.superMethod(arguments.callee, "メソッド名", var_args...) の arguments.callee の代わりにメソッド名を指定できます。
- つまり、以下のようにも書けます。
piyo: function Cpiyo() { // this.superMethod(arguments.callee, "piyo", 1, 2, 3); this.superMethod(Cpiyo, "piyo", 1, 2, 3); }
-
- Function.caller が使える環境なら、arguments.callee は不要なのですが、Opera9.5 で Function.caller が使えず、代替方法も無いため、呼び出し元の情報を渡すような I/F になっています。
- 継承元のクラス(SuperClass や SuperSuperClass)に動的に関数を追加しないで下さい。未定義の動作を引き起こします。
- 動的に関数を追加したい場合は継承させずに使ってください。
- シングルトンクラスを生成する uu.Class.singleton は継承をサポートしていません
- uu.Class.singleton("B<A") と書いても、思い通りには動きませんのでご注意を。