Menu
日本語

JavaScriptの継承 | extends・superと上書き

JavaScriptのクラス継承を基礎から整理。extendsでの継承、superの呼び出し、メソッドのオーバーライド、そして継承が向かない場面まで解説します。

クラスを別のクラスから派生させる

JavaScriptの継承を使うと、あるクラスを土台にして新しいクラスを作れます。親クラスが持っているプロパティやメソッドはそのまま引き継げるので、あとは必要な部分だけ追加したり書き換えたりすればOKです。

index.js
Output
Click Run to see the output here.

Dog extends Animal は「Dog は Animal の一種で、そこにちょっと足したもの」という意味です。rex 自身は speak メソッドを持っていませんが、探索が Animal まで辿り着き、そこで見つかるので呼び出せます。この「辿っていく」動きこそが継承の正体で、要はプロトタイプチェーンに見やすい構文を被せただけのものです。

コンストラクタ内の super

自前のコンストラクタを持つサブクラスには、絶対に守るべきルールが1つあります。それは、this を触る前に必ず super(...) を呼ぶこと。super は親クラスのコンストラクタを実行するもので、オブジェクトを実際に生成して初期化してくれるのはこの呼び出しです。

index.js
Output
Click Run to see the output here.

super(name) の行を飛ばすと、this を読み書きしようとした瞬間に ReferenceError が飛んできます。親クラスの処理が済むまで、エンジンは頑として this を使わせてくれないわけです。

サブクラスで独自のコンストラクタを書かない場合、JavaScript は引数をそのまま super に流すコンストラクタを自動で用意してくれます。なので、フィールドを追加したり初期化処理を足したいときだけ書けばOKです。

メソッドのオーバーライド

サブクラスは、継承したメソッドを自由に上書きできます。プロトタイプチェーン上で一番近いものが優先されるルールです。

index.js
Output
Click Run to see the output here.

ここは魔法でも何でもありません。Dog のインスタンスに対して speak() を呼ぶと、エンジンはまずインスタンス自身を探し、次に Dog.prototype を見に行きます。そこで見つかった時点で探索は打ち切りです。Animal.prototype まではたどり着きません。

置き換えではなく拡張する: super.method()

親のメソッドを完全に置き換えるのではなく、そこに処理を 足したい ケースもあります。そんなときは、オーバーライドの中から super.method(...) で親のメソッドを呼び出せます。

index.js
Output
Click Run to see the output here.

ここで継承の真価が発揮されます。サブクラスは親クラスのロジックをコピーするのではなく、そのまま再利用できるわけです。あとから Animal.describe を変更しても、Dog.describe は何もしなくてもその変更を取り込んでくれます。

super はコンストラクタの中だけでなく、どのメソッドの中でも使えます。呼び出そうとしているものの「親クラス側のバージョン」を常に指すと覚えておきましょう。

instanceof とプロトタイプチェーン

instanceof は、あるオブジェクトのプロトタイプチェーンに指定したクラスが含まれているかを調べる演算子です。サブクラスのインスタンスは、その親クラスのインスタンスでもあります。

index.js
Output
Click Run to see the output here.

4つすべて true になります。チェーンは Puppy -> Dog -> Animal -> Object の順でつながっていて、instanceof はこれを順にたどっていく仕組みです。型チェックには便利ですが、実際のコードでは思ったほど出番はありません。たいていはメソッドを呼んで、あとはポリモーフィズムに任せれば十分だからです。

もう少し実践的な例

よくあるパターンを見てみましょう。共通ロジックを持つ基底クラスがあり、それを特化させたサブクラスをいくつか用意する、という構成です。

index.js
Output
Click Run to see the output here.

注目してほしいのは、describeShape 側に定義したまま一度も書き直していない点です。中で呼んでいる this.area() が実行時に正しいサブクラスの実装に解決されるからですね。これがいわゆるポリモーフィズムで、呼び出し箇所は同じでも、実際のオブジェクトによって振る舞いが変わるわけです。

継承とコンポジションの使い分け

継承はつい使いたくなります。だって一行書くだけでメソッドがごっそり手に入るんですから。ただ、階層が深くなってくると途端に扱いづらくなります。

目安はシンプルで、「X は Y の一種である」という関係がハッキリしていて、サブクラスが親の振る舞いを本当にほぼそのまま引き継ぐときだけ extends を使う、ということ。ヘルパー的なメソッドを 1 つ 2 つ共有したいだけで継承に手を伸ばしているなら、コンポジションの方が向いています。つまり、ヘルパー役のオブジェクトをフィールドとして持たせる形にするわけです。

index.js
Output
Click Run to see the output here.

深い継承ツリー(Animal -> Mammal -> Dog -> WorkingDog -> PoliceDog のような構造)は図にすると美しく見えますが、コードになると途端に扱いづらくなります。ルートに近いところをちょっと変えただけで、その影響が子孫クラス全体に予測不能な形で波及してしまうからです。健全なコードベースの多くは継承を1〜2階層にとどめ、あとはコンポジションで組み立てています。

次は: 静的メンバー(static)

ここまで扱ってきたのはすべてインスタンスに属するもので、new Thing().something() のように呼び出すメソッドでした。でも、特定のインスタンスではなく「クラスそのもの」に紐づくメソッドやデータが欲しくなる場面もあります。そこで登場するのが static です。次はこれを見ていきましょう。

よくある質問

JavaScriptの継承ってどう動くの?

extendsを使うと、あるクラスを別のクラスから継承できます。サブクラスは親クラスのメソッドやフィールドをすべて引き継ぎ、新しいものを追加したり、既存のものを上書き(オーバーライド)したりできます。内部的には、サブクラスのプロトタイプが親クラスのプロトタイプにリンクされていて、メソッドが見つからなければ親側まで自動的にたどって探しにいく仕組みです。

superは何のために使うの?

super(...)は親クラスのコンストラクタを呼び出すためのもので、サブクラスのコンストラクタ内ではthisを使う前に必ず呼ぶ必要があります。一方super.method(...)は親クラスのメソッドを呼び出す書き方で、既存の処理を丸ごと置き換えるのではなく、拡張したいときに使います。

継承とコンポジション、どっちを使えばいい?

「BはAの一種である(is-a)」という関係が自然に成り立ち、サブクラスが親の振る舞いをほとんどそのまま使うなら継承が向いています。単に機能を使い回したいだけなら、オブジェクトの中に別のオブジェクトを持たせるコンポジションのほうが無難です。継承を深くしすぎると後で保守が苦しくなるので、実際のコードベースでは1〜2階層にとどめているケースがほとんどです。

Coddyでコードを学ぼう

始める