スポンサーリンク

JavaScriptで,クラスを継承する方法 (複数のサブクラスから共通クラスのプロトタイプを参照する)


JavaScriptのコーディングでオブジェクトを扱う際,

インスタンスがちょっとずつ異なっている」という場合は,その自由度に見合ったクラス(=プロトタイプ)を1つだけ定義してやれば事足りる。



しかし,それに加えて「クラスも複数あって,ちょっとずつ異なっている」という場合,

複数のクラスの共通部分を親クラスに定義し,子クラスで継承する,という手続きが必要になる。



親クラス,子クラス,インスタンス,という順に定義を実装すると,コードの流れは下記のようになる。

  • (1)共通定義
    • クラス間で共通のデータを定義する。
  • (2)個別定義(クラス生成コード)
    • クラス間で異なるデータを定義する。
    • ②同一クラス内のインスタンス間で共通のデータを定義する。
  • (3)メイン処理(インスタンス生成コード)
    • ①各クラスを利用する。同一クラス内のインスタンス間で異なるデータを定義する。
    • ②各インスタンスを利用する。


この流れに沿って,JavaScriptでクラスの継承を行なうサンプルコードを以下に掲載する。



本来記述したいコードは流れの最終部分の(3)であって,そのために(2)とか(1)のような下準備が必要になる。

なので,(1)から読み進めるとトップダウンでわかりにくいと思われる。

(3)の部分から読み進めると理解しやすいかもしれない。


// この行のコメントアウトを外せば,jsファイルを直接実行できます。
//function alert(s){ WScript.Echo(s) };




// ------------------- (1)共通定義 ---------------------



// 動物の基底クラス
// (具体的な動物クラスを定義するために使う共通のクラス)

var BaseAnimal = function( class_init_hash )
{
	// クラスの属性をまとめて定義
	this.type_name = class_init_hash[ "type_name" ];
	this.legs_num  = class_init_hash[ "legs_num" ];
};

BaseAnimal.prototype = {
	// クラスごとに異なる結果を返したい
	describe_animal_type : function()
	{
		alert( "種類は" + this.type_name + ",足は" 
		+ this.legs_num + "本です。" );
	}
	,
	
	// インスタンスごとに異なる結果を返したい
	introduce : function()
	{
		alert( "名前は" + this.personal_name + ",年は"
		 + this.age + "歳です。" );
	}
};


// 具体的な動物インスタンスの共通コンストラクタ
var common_constructor_for_animal_instance
		 = function( instance_init_hash )
{
	// インスタンスの属性をまとめて初期化
	this.personal_name = instance_init_hash[ "personal_name" ];
	this.age           = instance_init_hash[ "age" ];
};



// ------------------- (2)個別定義(クラス生成コード) ---------------------



// 亀クラス
var Turtle = function( instance_init_hash )
{
	// 動物インスタンスの共通コンストラクタを,
	// 新規オブジェクトのために呼び出し
	common_constructor_for_animal_instance.call(
		 this, instance_init_hash );
}
Turtle.prototype = new BaseAnimal({
	type_name : "亀",
	legs_num  : 4
});



// うさぎクラス
var Rabbit = function( instance_init_hash )
{
	// 動物インスタンスの共通コンストラクタを,
	// 新規オブジェクトのために呼び出し
	common_constructor_for_animal_instance.call(
		 this, instance_init_hash );
	
	// うさぎは毛の色も初期化時に指定できる事にする
	this.color = instance_init_hash[ "color" ]
}
Rabbit.prototype = new BaseAnimal({
	type_name : "うさぎ",
	legs_num  : 4
});

// うさぎは跳ねることも可能とする
Rabbit.prototype.jump = function()
{
	alert(this.personal_name + "がジャンプしました。");
}



// ------------------- (3)メイン処理(インスタンス生成コード) ---------------------



// 亀
var kame1 = new Turtle({
	personal_name : "亀夫",
	age           : 1
});
var kame2 = new Turtle({
	personal_name : "亀子",
	age           : 2
});
// クラスに固有のプロパティを呼び出し
kame1.describe_animal_type(); // 種類は亀,足は4本です。
kame2.describe_animal_type(); // 種類は亀,足は4本です。
// インスタンスに固有のプロパティを呼び出し
kame1.introduce();            // 名前は亀夫,年は1歳です。
kame2.introduce();            // 名前は亀子,年は2歳です。



// うさぎ
var usa1 = new Rabbit({
	personal_name : "うさ夫",
	age           : 3,
	color         : "白"
});
var usa2 = new Rabbit({
	personal_name : "うさ子",
	age           : 4,
	color         : "黒"
});
// クラスに固有のプロパティを呼び出し
usa1.describe_animal_type(); // 種類はうさぎ,足は4本です。
usa2.describe_animal_type(); // 種類はうさぎ,足は4本です。
// インスタンスに固有のプロパティを呼び出し
usa1.introduce();            // 名前はうさ夫,年は3歳です。
usa2.introduce();            // 名前はうさ子,年は4歳です。

// この動物クラスに固有のメソッドも呼べる
usa1.jump();                 // うさ夫がジャンプしました。
usa2.jump();                 // うさ子がジャンプしました。


ボトムアップで解説を掲載。


(3)①:同一クラス内のインスタンス間で異なるデータを定義するためには

子クラスのコンストラクタの引数にハッシュを渡すようにする。

new時に,そのハッシュの内容に応じた新規オブジェクトを返すことができる。



(2)②:同一クラス内のインスタンス間で共通のデータを定義するためには

(「静的」初期化の場合)

子クラスのprototype内に記述すればよい。

あるクラス内の全てのインスタンスが,同一のprototypeを共有するので。


(「動的」初期化の場合)

子クラスのコンストラクタ内に処理を書けばよい。



(2)①:クラス間で異なるデータを定義するためには

子クラス間で,prototypeの内容が異なればよい。

ただし,もしクラス間で共通化されたprototypeのコピーを使っている場合は,
該当するprototypeの定義後に,ドット記法によって差分を「追記」する事によって差異化する。
※「うさぎは跳ねることも可能とする」の部分。



(1)①:クラス間で共通のデータを定義するためには

ここがキモ。

赤字の3点がポイントになる。


(「静的」初期化の場合)


(a)親クラスにクラス間の共通のデータを書き,

(b)そして子クラスが親クラスを継承すればよい。

具体的に言うと

  • (a)あるクラスを基底クラスと考えることにし,基底クラスのprototype内に「クラス間共通データ」を書く
  • (b)newで生まれる基底クラスのインスタンスを,子クラスのprototypeに代入する。


こうすれば,「『同じに見えるけどオブジェクトの実体は異なる』ようなprototype」を複数生成することができる。

つまり,互いに独立してカスタマイズ可能な「そっくりさんクラス」を複数定義できる。

プロトタイプ継承によるクラスの継承 〜 JavaScript によるオブジェクト指向プログラミング
http://keicode.com/script/scr24f.php

  • 親クラスのインスタンスを,子クラスのprototypeに代入する。
  • 「この書き方によって、 子クラス の prototype オブジェクトと 親クラス の prototype オブジェクトがリンクされます。」
  • つまり,お手製のプロトタイプチェーンを作った,ということ。

Javascriptの継承
http://sein-und-zeit.seesaa.net/artic...

  • MozillaのJavascript解説でも、オライリーの例の本でも同様に行なわれいている継承方法が、 B.prototype = new A;
(「動的」初期化の場合)

子クラス間で,コンストラクタを共有すればよい。


そのためのひとつの方法は,

  • 子クラス間で共通のインスタンス初期化処理を独自の関数として定義し,
  • その関数を,子クラスの実コンストラクタ内で,callやapply付きで呼び出す


なぜcallやapplyが必要かというと,new付きで呼び出される関数(=コンストラクタ)の内部ではthisの意味が変わり,thisが新規オブジェクトを指すようになるから。

JavaScriptで,オブジェクトやクラスの初歩を理解しているか,実力を確かめるための7つの質問 (サンプルコード付き)
http://language-and-engineering.hatenablog.jp/entry/20100921/p1
1問目の解説を参照



クラスの継承の仕方
http://oshiete.goo.ne.jp/qa/1617352.html

  • 「コンストラクタから親のコンストラクタを呼び出すことで継承します。ただし、その際 this のメソッドとして呼ばないと、親コンストラクタでの初期化が自分に反映されません。」

補足

上記の例を超えて本格的にやる場合は,以下のamachangのページと,そこから辿られているリンクを全部熟読する必要がある。

本当は,クラスなんてものはない。

プロトタイプベースの継承(関数一発でプロトタイプチェーンに繋げて、オブジェクトをクローンする。)
http://d.hatena.ne.jp/amachang/200610...

とは言っても,

  • シンプルな設計で生産性の高いWebアプリ開発を行なうことが目的で,
  • サーバサイドもシステム境界内であり,
  • Javaのバックグラウンドを持った技術者が集めやすい状況

というような場合,今回取り上げたサンプルで事足りると思われる。

(「クラス」という言葉を使って説明しているのはそのため)