https://idea-hack.com/courses/lets-understand-how-to-use-scope-function-class-in-javascript/lessons/lets-understand-how-to-inherit-classes-in-javascript/
https://idea-hack.com/courses/lets-understand-how-to-use-scope-function-class-in-javascript/lessons/lets-understand-how-to-inherit-classes-in-javascript/

JavaScriptでクラスを継承する方法を理解しよう

JavaScriptでクラスを継承とは?

クラスには、あるクラスのクラス変数やクラスメソッドを引継ぎ、新しいクラスを作成する「継承」という機能があります。

構文

構文意味
class クラス名 extends 親クラス名{}親クラスを継承して新しいクラスを作成
superconstructor内またはクラスメソッド内において、親クラスのコードをそのまま引き継ぐために使う
継承クラスの仕様

継承の利用目的

継承クラスの目的は主に以下の2つです。

  • 既存の親クラスで定義されている内容をオーバーライドする
  • 既存の親クラスで定義されている内容にプラスして新しいクラスメソッドなどを定義する

それでは、確認していきましょう。

継承クラスの使い方

次の例では、TargetClassParentクラスを継承したTargetClassChildを定義しています。

継承クラス内で親クラスで定義されたクラスメソッドと同じ名前のものを定義することで、親クラスのオーバーライドが行えます。

親クラスを定義

例えば、下記の様な親クラスを定義したとします。

JavaScript
const targetText = document.getElementById( 'targetText' );
const targetText2 = document.getElementById( 'targetText2' );
const targetText3 = document.getElementById( 'targetText3' );

//クラスを定義処理の塊
class targetClass {
  constructor(value1,value2) {
		//class内で変数を定義
		this.targetVariable1 = value1;
		//class内で変数を定義
		this.targetVariable2 = value2;
		//class内で変数を用いた処理
		targetText.textContent = `${this.targetVariable1}:${this.targetVariable2}`;
  }
	//class内で関数を定義
	targetMethod() {
		targetText2.textContent = `私はメソッドです。受け取った引数は「${this.targetVariable1}」と「${this.targetVariable2}」だよ。`;
	}
	
	//class内のスタティックメソッドを定義
	static targetmethodStatic(firstName,lastName) {
		targetText3.textContent = `私は「${firstName}${lastName}」です。スタティックメソッドの引数はこの様に受け取れる。だけどクラスの方で受け取った引数は「${this.targetVariable1}」と「${this.targetVariable2}」だよ。要するに私はスタティックメソッドなので、Constroctorの値は受け取らないよ。引数入れても無駄`;
	}
}

//Classのインスタンス化(Class内の処理の元になるデータを引数として渡す)
const targetInstance = new targetClass('User A', 1);

//targetInstanceの中のtargetmethodを実行
targetInstance.targetMethod();

//Staticメソッドを実行
targetClass.targetmethodStatic('Sample', 'Person');

この場合は、出力結果は下記の様になります。

コンストラクター User A:1
クラスメソッド 私はメソッドです。受け取った引数は「User A」と「1」だよ。
スタティックメソッド 私は「SamplePerson」です。スタティックメソッドの引数はこの様に受け取れる。だけどクラスの方で受け取った引数は「undefined」と「undefined」だよ。要するに私はスタティックメソッドなので、Constroctorの値は受け取らないよ。引数入れても無駄

継承クラスを定義

それでは、継承クラスを下記の様に定義します。今回の用意したコードは下記の通りです。

JavaScript
class TargetChildClass extends targetClass{
	constructor(value1,value2,value3) {
		super(value1,value2);
		//class内で変数を定義
		this.targetVariable3 = value3;
  }
	//親class内の定義をオーバーライド
	targetMethod() {
		targetText2.textContent = `私は継承メソッドです。親クラスの内容を書き換えてやったぜ。ちなみに継承クラスのインスタンスの引数は「${this.targetVariable3}」だぜ。`;
	}
}

継承クラスをインスタンス化

継承クラスをインスタンス化します。この時、親クラスで定義されるクラス変数向けの引数(サンプルコードの場合は'User A',1)を忘れずに代入してください。

ここに代入された値が親クラスに渡されます。

JavaScript
//Classのインスタンス化(Class内の処理の元になるデータを引数として渡す)
const TargetChildInstance = new TargetChildClass('User A', 1, 'アウト');

親クラスと継承クラスの完成形

そしてインスタンス化された継承クラスのクラスメソッドやコンストラクターを実行した結果が下記の通りです。こちらが完成形になります。

Output
コンストラクター
クラスメソッド
スタティックメソッド
継承クラスの追加メソッド
Pug
table.table
			tbody
				tr
					th コンストラクター
					td#targetText
				tr
					th クラスメソッド
					td#targetText2
				tr
					th スタティックメソッド
					td#targetText3
				tr
					th 継承クラスの追加メソッド
					td#targetText4
JavaScript
const targetText = document.getElementById( 'targetText' );
const targetText2 = document.getElementById( 'targetText2' );
const targetText3 = document.getElementById( 'targetText3' );
const targetText4 = document.getElementById( 'targetText4' );

//クラスを定義処理の塊
class targetClass {
  constructor(value1,value2) {
		//class内で変数を定義
		this.targetVariable1 = value1;
		//class内で変数を定義
		this.targetVariable2 = value2;
		//class内で変数を用いた処理
		targetText.textContent = `${this.targetVariable1}:${this.targetVariable2}`;
  }
	//class内で関数を定義
	targetMethod() {
		targetText2.textContent = `私はメソッドです。受け取った引数は「${this.targetVariable1}」と「${this.targetVariable2}」だよ。`;
	}
	
	//class内のスタティックメソッドを定義
	static targetmethodStatic(firstName,lastName) {
		targetText3.textContent = `私は「${firstName}${lastName}」です。スタティックメソッドの引数はこの様に受け取れる。だけどクラスの方で受け取った引数は「${this.targetVariable1}」と「${this.targetVariable2}」だよ。要するに私はスタティックメソッドなので、コンストラクターの値は受け取らないよ。引数入れても無駄`;
	}
}

class TargetChildClass extends targetClass{
	constructor(value1,value2,value3) {
		super(value1,value2);
		//class内で変数を定義
		this.targetVariable3 = value3;
  }
	//親class内の定義をオーバーライド
	targetMethod() {
		targetText2.textContent = `私は継承メソッド 親クラスの内容を書き換えてやったぜ。ちなみに継承クラスのインスタンスの引数は「${this.targetVariable3}」だぜ。`;
	}
	
	//継承クラス専用の追加メソッド
	taregetChildClassMethod() {
		targetText4.textContent = `私は継承メソッド 親クラスの内容を書き換えたのではなく、継承クラスで新定義されたメソッドです。ちなみに親クラスのインスタンスの引数はは「${this.targetVariable1}」と「${this.targetVariable2}」だよ。`;
	}
}

//Classのインスタンス化(Class内の処理の元になるデータを引数として渡す)
const TargetChildInstance = new TargetChildClass('User A', 1, 'アウト');

//targetInstanceの中のtargetmethodを実行
TargetChildInstance.targetMethod();

//継承クラス専用のメソッドを実行
TargetChildInstance.taregetChildClassMethod();

//Staticメソッドを実行
TargetChildInstance.targetmethodStatic('Sample', 'Person');

それでは、継承クラスで知っておくべきことをまとめていきます。

継承クラスの解説

コンストラクターのsuper();とは?

継承クラス内のコンストラクターの定義が下記の様になっています。

JavaScript
constructor(value1,value2,value3) {
		super(value1,value2);
		//class内で変数を定義
		this.targetVariable3 = value3;
  }

なので、これまでの説明と照らし合わせると、親クラスのオーバーライドが行われています。これは正しい解釈です。

しかし、継承クラスと親クラスのサンプルコードの出力結果は同じ値(User A;1)です。何故でしょうか?

super();は親クラスの内容をそのまま引き継ぐ特別なメソッドです。

このメソッドを用いているため、親クラスの内容をそのまま継承クラスに反映していることになります。

なので、親クラスのコンストラクターを継承クラスのコンストラクターで継承してはいるものの、super();を使っているため、最終的には下記のコードが実行され、サンプルコードの出力結果は同じ値(User A;1)です。

JavaScript
constructor(value1,value2) {
		//class内で変数を定義
		this.targetVariable1 = value1;
		//class内で変数を定義
		this.targetVariable2 = value2;
		//class内で変数を用いた処理
		targetText.textContent = `${this.targetVariable1}:${this.targetVariable2}`;
  }

また、継承クラスのコンストラクターに記載されているthis.targetVariable3 = value3;は継承クラス内でのみ利用可能なクラス変数であり、後述するクラスメソッドで利用されています。

super()

継承クラス内のsuper()には以下のルールがあります。

コンストラクター内での位置

コンストラクター内でsuper()を定義するなら、一番上でなくてはならない。つまり、下記の様に書かなければならない。

JavaScript
constructor(value1,value2,value3) {
		super(value1,value2);
		//class内で変数を定義
		this.targetVariable3 = value3;
  }

下記の様な書き方は誤りです。

JavaScript
constructor(value1,value2,value3) {
		//class内で変数を定義
		this.targetVariable3 = value3;
    super(value1,value2);
  }

super()はクラスメソッドでも利用可能

本ページのサンプルコードではsuper()をコンストラクター内でのみ記述しましたが、もちろん継承クラス内のクラスメソッドでも利用可能です。役割は同じで、親クラスのクラスメソッドの内容を引継ぎます。

super()の【引数】を忘れずに

継承クラスをインスタンス化するする際に、親クラスに渡すための引数の定義を忘れることが多いです。(これで半日くらい時間潰す人もいるくらい。)

例えば、下記のコードでは引数を3つ渡しています。

JavaScript
constructor(value1,value2,value3) {
		super(value1,value2);
		//class内で変数を定義
		this.targetVariable3 = value3;
  }

このうち、2つの引数(value1,value2)は親クラスのコンストラクターで利用されるため必要です。

JavaScript
constructor(value1,value2) {
		//class内で変数を定義
		this.targetVariable1 = value1;
		//class内で変数を定義
		this.targetVariable2 = value2;
		//class内で変数を用いた処理
		targetText.textContent = `${this.targetVariable1}:${this.targetVariable2}`;
  }

継承クラスでは利用しないので、忘れがちになります。注意しましょう。

スタティックメソッドの値が空

スタティックメソッドの値が空です。

下記の記述は誤りだからです。

JavaScript
TargetChildInstance.targetmethodStatic('Sample', 'Person');

スタティックメソッドは継承できませんので、もし、スタティックメソッドを利用したい場合は、継承の有無に関係なく、親クラスを直接指定して実行する必要があります。

つまり、正しいコードは下記の記述です。

JavaScript
targetClass.targetmethodStatic('Sample', 'Person');

クラスメソッドの値が変わっている

クラスメソッドの値が変わっていることがわかります。これは、親クラスと同じクラスメソッドを継承クラスでも定義しているためです。

親クラスでは下記の様に定義されてました。

JavaScript
//class内で関数を定義
	targetMethod() {
		targetText2.textContent = `私はメソッドです。受け取った引数は「${this.targetVariable1}」と「${this.targetVariable2}」だよ。`;
	}
	

一方、継承クラス内では下記の様に定義されてました。

JavaScript
//親class内の定義をオーバーライド
targetMethod() {
		targetText2.textContent = `私は継承メソッドです。親クラスの内容を書き換えてやったぜ。ちなみに継承クラスのインスタンスの引数は「${this.targetVariable3}」だぜ。`;
	}

これにより、継承クラスのインスタンスでは、オーバーライドが実行されています。

また、上記のコードでは、継承クラスのコンストラクター内でthis.targetVariable3 = value3;を定義しており、この値の出力を行っています。

継承クラス専用のメソッド

継承クラス内で新しいメソッドが追加されています。該当するコードは下記です。

JavaScript
//継承クラス専用の追加メソッド
	taregetChildClassMethod() {
		targetText4.textContent = `私は継承メソッド 親クラスの内容を書き換えたのではなく、継承クラスで新定義されたメソッドです。ちなみに親クラスのインスタンスの引数はは「${this.targetVariable1}」と「${this.targetVariable2}」だよ。`;
	}

この様に、継承クラス内で新しいクラスメソッドを定義することで、親クラスの拡張が可能です。

また、継承クラスのコンストラクターでsuper()が実行されています。

JavaScript
constructor(value1,value2,value3) {
		super(value1,value2);
		//class内で変数を定義
		this.targetVariable3 = value3;
  }
	//親class内

このため、親クラスのコンストラクターを読み込んでいます。

JavaScript
constructor(value1,value2) {
		//class内で変数を定義
		this.targetVariable1 = value1;
		//class内で変数を定義
		this.targetVariable2 = value2;
		//class内で変数を用いた処理
		targetText.textContent = `${this.targetVariable1}:${this.targetVariable2}`;
  }

よって、継承クラス内で追加されたクラスメソッド内でthis.targetVariable1this.targetVariable2の参照が可能です。

インストラクター