6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年1月21日 JavaScript (JS Primer) 継承

本日はクラスの継承について勉強しました。

継承

extendsキーワードを使うことで既存のクラスの構造や機能を引き継いだ新しいクラスを定義することが出来ます。

クラスの定義

以下のように定義します。

class 子クラス extends 親クラス {
    ~子クラスの定義~
}

Rubyの場合は以下のようになります。

class クラス名 < 継承したいクラス名
end

super

extendsを使って定義した子クラスから親クラスを参照するにはsuperというキーワードを利用します。

クラスは必ずconstructorメソッド(コンストラクタ)を持ちます。 これは、継承した子クラスでも同じです。

次のコードでは、Parentクラスを継承したChildクラスのコンストラクタで、super()を呼び出しています。 super()は子クラスから親クラスのconstructorメソッドを呼び出します。

// 親クラス
class Parent {
    constructor(...args) {
        console.log("Parentコンストラクタの処理", ...args);
    }
}
// Parentを継承したChildクラスの定義
class Child extends Parent {
    constructor(...args) {
        // Parentのコンストラクタ処理を呼び出す
        super(...args);
        console.log("Childコンストラクタの処理", ...args);
    }
}
const child = new Child("引数1", "引数2");
// "Parentコンストラクタの処理", "引数1", "引数2"
// "Childコンストラクタの処理", "引数1", "引数2"

class構文でのクラス定義では、constructorメソッド(コンストラクタ)で何も処理しない場合は省略できることを紹介しました。 これは、継承した子クラスでも同じです。

次のコードのChildクラスのコンストラクタでは、何も処理を行っていません。 そのため、Childクラスのconstructorメソッドの定義を省略できます。

class Parent {}
class Child extends Parent {}

このように子クラスでconstructorを省略した場合は次のように書いた場合と同じ意味になります。 constructorメソッドの引数をすべて受け取り、そのままsuperへ引数の順番を維持して渡します。

class Parent {}
class Child extends Parent {
    constructor(...args) {
        super(...args); // 親クラスに引数をそのまま渡す
    }
}

復習

  • Rest parametersは、仮引数名の前に...をつけた仮引数のことで、残余引数とも呼ばれます。 Rest parametersには、関数に渡された値が配列として代入されます。

  • Spread構文は関数を呼び出す際に使用します。配列やオブジェクトを展開することができます。

const array = [1, 2, 3];
// Spread構文で配列を引数に展開して関数を呼び出す
fn(...array);
// 次のように書いたのと同じ意味
fn(array[0], array[1], array[2]);

console.log(...array); // => 1, 2, 3

コンストラクタの処理順は親クラスから子クラスへ

コンストラクタの処理順は、親クラスから子クラスへと順番が決まっています。

class構文では必ず親クラスのコンストラクタ処理(super()の呼び出し)を先に行い、その次に子クラスのコンストラクタ処理を行います。 子クラスのコンストラクタでは、thisを触る前にsuper()で親クラスのコンストラクタ処理を呼び出さないとReferenceErrorとなるためです。

次のコードでは、ParentとChildでそれぞれインスタンス(this)のnameプロパティに値を書き込んでいます。 子クラスでは先にsuper()を呼び出してからでないとthisを参照できません。 そのため、コンストラクタの処理順はParentからChildという順番に限定されます

class Parent {
    constructor() {
        this.name = "Parent";
    }
}
class Child extends Parent {
    constructor() {
        // 子クラスでは`super()`を`this`に触る前に呼び出さなければならない
        super();
        // 子クラスのコンストラクタ処理
        // 親クラスで書き込まれた`name`は上書きされる
        this.name = "Child";
    }
}
const parent = new Parent();
console.log(parent.name); // => "Parent"
const child = new Child();
console.log(child.name); // => "Child"
  • 仮にsuper();の前に子クラスの別の処理を書いた場合は、以下のエラーが発生します。

    ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
    英訳: ReferenceError: this' にアクセスする前、または派生クラスのコンストラクタから戻る前に、派生クラスのスーパーコンストラクタを呼び出す必要があります。

継承した子クラスのコンストラクタ関数内ではsuper を使用して親クラスのコンストラクタ関数を呼び出さないとエラーになります。

コメント

今回は特に深堀りするところはなかったので、書籍の内容を理解して覚えることに重点を置いた内容になりました。

子クラスのコンストラクタに処理を書く場合は必ず親クラスのコンストラクタを呼び出す記述を先に書かないといけないということが大事だと思いました。

参考

JS primer 継承