JavaScriptの継承を実装する

JavaScriptには継承の機能が無い(代わりにプロトタイプチェーンを使用して同等の機能を実現する)わけですが、一つ自分好みの継承関数を実装してみたいと思います。
ソース:inherit.js

前提はこれくらいでしょうか

  1. オーバーライド時に継承元の関数が呼び出せる
  2. 多階層の継承が出来る
  3. C++でのvirtualな呼び出しが出来る

C++やC#をずっと使ってきた自分にとってはプロトタイプチェーンだけではVB6のClassのような残念感があります。
やはりポリモーフィズムやオーバーライドが出来ないと逆に不便です。
色々な継承パターンを調べてみましたが、extendパターンが自分向きのようです。

まずは単純なextend関数

function extend(s, c)
{
    function f(){};
    f.prototype = s.prototype;
    c.prototype = new f();
    c.prototype.__super = s.prototype;
    c.prototype.__super.constructor = s;
    c.prototype.constructor = c;
    return c;
};

いきなり余談ですが、JavaScriptではインスタンスも拡張できるので以下のような書き方が出来れば、メタクラスの概念も導入できそうですが、
私は使わないので考慮しないことにします。

MetaClass = function() {}
Class = new MetaClass() {}  //クラスはメタクラスのインスタンス
SubClass = extend(Class, function() {
});

閑話休題。
次にコンストラクターをC#やVB.Netの雰囲気で呼び出せるようにbaseメソッドを追加します。

function extend(s, c)
{
    function f(){};
    f.prototype = s.prototype;
    c.prototype = new f();
    c.prototype.__super = s.prototype;
    c.prototype.__super.constructor = s;
    c.prototype.constructor = c;
    c.prototype.base = s;
    return c;
};

と、これをやると多層継承で嵌ります。

a = function() {}
b = extend(a, function() {
  this.base(); //ここのthisがクラスbを指し続けてしまう。
});
c = extend(b, function() {
  this.base();
});
var foo = new c();

なのでbaseを関数で実装する

function extend(s, c)
{
    function f(){};
    f.prototype = s.prototype;
    c.prototype = new f();
    c.prototype.__super = s.prototype;
    c.prototype.__super.constructor = s;
    c.prototype.constructor = c;
    c.prototype.base = function() {
      var ob = this.base;
      this.base = s.prototype.base;
      s.apply(this, arguments);
      if( this.constructor == c) {//baseを一回しか呼べないように削除しておく
          delete this.base;
      } else {
          this.base = ob;
      }
    }
    return c;
};

次はメソッドのオーバーライドです。以下のやり方では、bでオーバーライドしたbarが期待通り呼び出されません。

a = function() {};
a.prototype.foo = function() { 
  this.bar(); //bのbarを読んでくれません。
}
a.prototype.bar = function() {
  this._bar = "bar";
}

b = extend(a, function() {
  this.base();
});
b.prototype.foo = function() {
  this.__super.foo();
}
b.prototype.bar = function() {
 this.__super.bar();
  this._bar += "b";
}

var hoge = new b();
hoge.foo();

これは、this.__super.foo();this.__super.foo.apply(this,arguments);とすることで解決します。

ところが、この記述はbaseと同様に多層継承での問題をはらんでいます。

結局、ここについては専用の関数を提供することにしました。

__super__ = function(me, m, a) {
    a = a || [];
    var b = me.__super;
    var r,s;
    if (b && b[f] == me[f]) {
        for (s = me.__super.__super; s && s[f] == me[f]; s = s.__super) {
            ;
        }
    } else {
        s = b;
    }
    if (s == null) {
        throw new Error("__dqsuper__: 循環呼び出しか、継承されていないオブジェクトから呼び出されました。");
    }
    me.__super = s.__super;
    r = (typeof f == "string") ? s[f].apply(me, a) : f.apply(me, a);
    me.__super = b;

    return r;
}

一応完成形

function extend(s, c, m) {
    function f(){};
    f.prototype = s.prototype;
    c.prototype = new f();
    c.prototype.__super = s.prototype;
    c.prototype.__super.constructor = s;
    c.prototype.constructor = c;
    c.prototype.base = function() {
      var ob = this.base;
      this.base = s.prototype.base;
      s.apply(this, arguments);
      if( this.constructor == c) { 
          delete this.base;
      } else {
          this.base = ob;
      }
    }
    //属性を拡張
    for (var n in m) {
        c.prototype[n] = m[n];
    }
    return c;
}

デモ
JavaScript Arrayの継承について

M. K. の紹介

IT屋さんです。プログラミングが大好きで今はJavascriptがお気に入りです。
カテゴリー: JavaScript, tips   タグ: ,   この投稿のパーマリンク