JavaScriptでclassを継承する

JavaScriptでclassをシュミレートする方法

JavaScriptはprototypeベースのオブジェクト指向で、classという機能は現時点ではありません。
prototypeとは、「原型」という意味です。JavaScriptではあるオブジェクトが他のオブジェクトの原型となることで、オブジェクトの拡張・継承がなされます。一つの具体的な手順としては、オブジェクトのコンストラクタとして関数を利用し、その関数のprototypeプロパティに原型オブジェクトを設定します。
このような手順を取る場合には、関数がオブジェクトを作ったり、繋げたりする機能を果たすということになります。関数のprototypeプロパティには、どのようなオブジェクトでも設定することができますが、「原型」としてのみ利用するオブジェクトを設定することで、関数をclassに見立たてたclassベースのオブジェクト指向をシュミレートすることができます。
classに見立てた関数に、毎回その継承手順を行うのですが、これは継承設定の柔軟性の確保にはなりますが、多少手間がかかります。そこで、基本的な継承手順を自動化した関数を作ります。
継承手順には、大きくプロトタイプチェーン、コンストラクタチェーン、メソッドチェーンがありますが、プロトタイプチェーン関数だけでも作っておくと作業効率が上がります。

以下に、私が作った継承関数を紹介します。プロトタイプチェーンだけではなく、コンストラクタチェーン、メソッドチェーンも楽に作業できるように工夫しました。(メソッドチェーンは、親クラスへの参照があるというだけですが、、。)

関数名: inherit

引数: 親class、子classのコンストラクタ
返値: 子class

説明:

JavaScriptで、classに見立てた関数の継承作業を自動化する関数です。
第一引数に親class、第二引数に子classのコンストラクタを取り、返値として子classを返します。
子classのプロパティ.parentCtorに、親classのコンストラクタが定義されます。これをコンストラクタチェーンに使ってください。
子classのプロパティ.parentに、親classへの参照が定義されます。これをメソッドチェーンに使ってください。

仕様解説

特色

JavaScriptでは、関数がコンストラクタになるので、その関数をclassに見立てると解説しましたが、このclass継承関数inheritでは、classに見立てる関数とは別に、それとセットとなるコンストラクタ関数を用意しました。これによりclass継承作業の柔軟性を確保します。

コスト

子class生成時のinherit実行コールオブジェクト。
子classとセットになるコンストラクタ。上記コールオブジェクトに格納される。

実行フロー

inherit実行時:

子classの定義
プロトタイプチェーン
親classへの参照設定
子classを返す → inherit実行コールオブジェクト残る

子class実行時:

子classに親コンストラクタ(を実行する関数parentCtor、以下同じ)を設定
→ 子class実行コールオブジェクト残る
子classのコンストラクタを実行 → 親コンストラクタを実行
親コンストラクタを廃棄 → 子class実行コールオブジェクト廃棄

使用例:

//親class宣言
function class1(test1,test2){
	this.test1 = test1;
	this.test2 = test2;
}

//子class宣言
var class2 = inherit(class1,function(test1,test3){
//コンストラクタチェーン。引数test2をバインド。
	class2.parentCtor(test1,10); 
	this.test3 = test3;
});

関数のコード:

function inherit(parent,ctor){
        
    //子classを定義する。
    function child(){
        
        if(ctor){
            
            //コンストラクタチェーンをchildにセットする。
            var itself = this;
            child.parentCtor = function(){
                parent.apply(itself,arguments);
            }
            
            //コンストラクタを実行する。
            ctor.apply(this,arguments);
            
            //実行済みの親コンストラクタを廃棄する。
            delete child.parentCtor;
            
        }
    }
    
    //プロトタイプチェーン。
    function temClass(){} //プロトタイプのみ継承するため。
    temClass.prototype = parent.prototype;
    child.prototype = new temClass();
    //constructorプロパティを修正する
    child.prototype.constructor = child;
    
    //メソッドチェーンのため、親classへの参照を格納。
    child.parent = parent;
    
    return child;
};

動作確認用コード:

以下のコードは、jsdo.itで試行できます。
親class1、子class2、孫class3

//class関数の定義
var Utility = {};
Utility.inherit = function(parent,ctor){
	function child(){
		if(ctor){
			var itself = this;
			child.parentCtor = function(){
				parent.apply(itself,arguments);
			}
			ctor.apply(this,arguments);
			delete child.parentCtor;
		}
	}
	function temClass(){}
	temClass.prototype = parent.prototype;
	child.prototype = new temClass();
	child.prototype.constructor = child;
	child.parent = parent;
	return child;
};

//親class1の定義
function class1(test1,test2){
	this.test1 = test1;
	this.test2 = test2;
}
//親class1のメソッド定義
class1.prototype.display1 = function(){
	alert(this.test1);
};
class1.prototype.display2 = function(){
	alert(this.test2);
};

//子class2の定義
var class2 = Utility.inherit(class1,function(test1,test3){
	//コンストラクタチェーン。引数test2をバインド。
	class2.parentCtor(test1,10); 
	this.test3 = test3;
});
//子class2のメソッド定義
class2.prototype.display3 = function(){
	alert(this.test3);
};

//孫class3の定義
var class3 = Utility.inherit(class2,function(test1){
	//コンストラクタチェーン。引数test3をバインド。
	class3.parentCtor(test1,5);
});

//子class2のオブジェクト生成
var class2Obj = new class2(1,2);
//テスト、メソッド実行
class2Obj.display1();	//1
class2Obj.display2();	//10
class2Obj.display3();	//2

//孫class3のオブジェクト生成
var class3Obj = new class3(1);
var class3Obj2 = new class3(2);
//テスト、メソッド実行、class3Obj
class3Obj.display1();	//1
class3Obj.display2();	//10
class3Obj.display3();	//5
//テスト、メソッド実行、class3Obj2
class3Obj2.display1();	//2
class3Obj2.display2();	//10
class3Obj2.display3();	//5

参考文献:

JavaScript 第5版, David Flanagan, 訳:村上 列, 株式会社オライリー・ジャパン, 2009年3月16日
クラスの継承の仕方 – JavaScript – 教えて!goo, http://oshiete.goo.ne.jp/qa/1617352.html, 2005年8月31日