【JavaScript】クラス⑤ 〜継承〜

# 概要
JavaScriptを学習、理解を深めるため「[JavaScript Primer 迷わないための入門書](https://jsprimer.net/)」を読み、
理解した内容等を記載していく。

「[【JavaScript】JavaScript入門一覧](https://qiita.com/andota05/items/22fae452225b785fd405)」に他の記事をまとめています。

# この記事で理解できること
* クラスの継承とは
* 継承の方法
* 継承時の各挙動(コンストラクタ、フィールドなど)

# 継承
* 継承とは、`クラスの構造や機能`を引き継いだ新しいクラスを定義すること。
* `extends`キーワードを使って既存のクラスを継承した新しいクラスを定義できる。
* 継承されるクラスを`親クラス`、親クラスを継承するクラスを`子クラス`と呼んだりする。

```js
// 継承される側
class Parent {
}

// extendsキーワードで親クラス(ここではParent)を継承して新しいクラスを作成
class Child extends Parent {
}
```

# superキーワード
* 継承した子クラスで親クラスのコンストラクタ(初期化処理)を`super()`とすることで呼び出せる。

```js
// 継承される側
class Parent {
  constructor() {
    console.log("親クラスのコンストラクタ");
  }
}

// extendsキーワードで継承して新しいクラスを作成
class Child extends Parent {
  constructor() {
    super();
    console.log("子クラスのコンストラクタ");
  }
}

const instance = new Child();
// => 親クラスのコンストラクタ
// => 子クラスのコンストラクタ
```

# コンストラクタの処理順
* class構文では`親クラスのコンストラクタ処理`を先に行い、その次に`子クラスのコンストラクタ処理`を行う必要がある。
* 子クラスのコンストラクタで、thisを触る前に親クラスのコンストラクタ処理`super()`を呼び出さないと`ReferenceError`となる。

```js
class Parent {
  constructor() {
    this.name = "Parent";
  }
}

class Child extends Parent {
  constructor() {
    // super()をコメントアウトして試すとReferenceErrorになる
    super();
    this.name = "Child";
    console.log(this.name);
  }
}

const instance = new Child(); // => Child
```

# クラスフィールドの継承
* Publicクラスフィールドもコンストラクタの処理順と同じく、`親クラス`のフィールドが初期化された後に`子クラス`のフィールドが初期化される。
* 親クラスで定義されていたフィールドも、実際にインスタンス化したオブジェクトのプロティとして定義される。
* Privateクラスフィールドは、親クラスで定義したフィールドは子クラスに定義されない(親クラス内でのみ参照可能)。
* JavaScriptでは、`クラスの外に公開したくないが、子クラスからは利用できるようにしたい`というような中間の制限を持ったプロパティを定義する構文はない。

```js
class Parent {
  parentName = "Parent";
  #privateParent = "親クラス内でのみ参照できる"; 
}

class Child extends Parent {
  childName = "Child";
}

const instance = new Child();

console.log(instance.parentName);      // => Parent
console.log(instance.childName);       // => Child
console.log(instance.#privateParent);  // => SyntaxError: Private field '#privateParent' must be declared in an enclosing class
```

# プロトタイプ継承
* 子クラスのインスタンスから親クラスのプロトタイプメソッドもプロトタイプチェーンの仕組みによって呼び出せる。
* class構文では`プロトタイプオブジェクト`を参照する仕組みによって継承が行われ、この継承の仕組みを`プロトタイプ継承`と呼ぶ。
* `静的メソッド`も同様に継承される。

```js
class Parent {
  parentName = "Parent";
  
  parentMethod() {
    console.log("親クラスのプロトタイプメソッド");
  }
}

class Child extends Parent {
  childName = "Child";
}

const instance = new Child();

instance.parentMethod(); // => 親クラスのプロトタイプメソッド
/**
 * parentMethodメソッドが呼ばれるまでの流れ
 * ①インスタンス(instance)内を探す       -> 存在しない
 * ②Child.prototype内部プロパティを探す  -> 存在しない
 * ③Parent.prototype内部プロパティを探す -> 存在する
 */
```

# superプロパティ
* 子クラスのプロトタイプメソッドからは、super.プロパティ名で親クラスのプロトタイプメソッドを参照できる。
* 子クラスから親クラスのプロトタイプメソッドへのアクセスは`super.プロパティ(メソッド)名`で参照できる。

```js
class Parent {
    method() {
        console.log("親クラスのメソッド");
    }
}

class Child extends Parent {
    method() {
        console.log("子クラスのメソッド");
        super.method();
    }
}

const child = new Child();
child.method();
// => 子クラスのメソッド
// => 親クラスのメソッド
```

# 継承の判定
* あるクラスが指定したクラスをプロトタイプ継承しているかは`instanceof演算子`を使って判定できる。

```js
class Parent {}
class Child extends Parent {}
class OtherChild {}

const child = new Child();
const otherChild = new OtherChild();

extendParent(child);      // => Parentを継承しています
extendParent(otherChild); // => Parentを継承していません

function extendParent(instance) {
  if (instance instanceof Parent) {
    console.log("Parentを継承しています");
  } else {
    console.log("Parentを継承していません");
  }
}
```