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日