JavaScript | ES6+ 文法:クラス構文 – 継承(extends)

JavaScript JavaScript
スポンサーリンク

継承(extends)とは何か(まずイメージから)

継承(extends)は、
「あるクラスをベースにして、それを少しだけ“追加・変更”したクラスを作る仕組み」 です。

「動物」という共通部分を持つクラスがあって、
そこから「犬」「猫」などのクラスを作るイメージです。

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} が何かを喋った`);
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name}「ワン!」`);
  }
}

const d = new Dog("ポチ");
d.speak(); // ポチ が何かを喋った(Animal のメソッド)
d.bark();  // ポチ「ワン!」(Dog 独自のメソッド)
JavaScript

ここが重要です。
extends を使うと、「共通部分(Animal)」と「種類ごとの違い(Dog)」を綺麗に分けられます。
同じようなコードをコピペせずに、重なる部分は親クラスにまとめておけるわけです。


extends の基本構文と「親子関係」

親クラスと子クラスの関係

class 子 extends 親 という形で書きます。

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} が何かを喋った`);
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name}「ワン!」`);
  }
}
JavaScript

DogAnimal を継承しているので、Animal が持つプロパティやメソッドを引き継ぎます。

const d = new Dog("ポチ");

d.speak(); // 親クラス Animal のメソッド
d.bark();  // 子クラス Dog のメソッド

console.log(d instanceof Dog);    // true
console.log(d instanceof Animal); // true(Animal の一種でもある)
JavaScript

ここが重要です。
Dog から作られたインスタンスは、「Dog のインスタンス」でありつつ「Animal のインスタンスでもある」という二重の顔を持ちます。
これが「〜の一種」という継承の考え方です。


子クラスの constructor と super(ここがつまずきポイント)

子クラスで constructor を定義する場合のルール

親クラスに constructor があるとき、子クラスで自分の constructor を定義したい場合、
必ず最初に super(...) を呼ばなければなりません。

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);      // 親(Animal)の constructor を呼ぶ(必須)
    this.breed = breed;
  }

  info() {
    console.log(`${this.name} (${this.breed})`);
  }
}

const d = new Dog("ポチ", "柴犬");
d.info(); // ポチ (柴犬)
JavaScript

super(name) は、「親クラスの constructor(name) を呼ぶ」という意味です。
これによって、this.name の初期化は親クラスに任せつつ、breed は子クラスで追加しているわけです。

ここが重要です。
子クラスの constructor では、super(...) より前に this を触るとエラーになります。
extends しているクラスに constructor を書いたら、最初に super(...)」と機械的に覚えておくと楽です。

親の constructor をそのまま使うだけなら、子は書かなくてよい

もし子クラス側で特別な初期化が必要ないなら、constructor 自体を省略できます。

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {
  // constructor を書かなければ、
  // constructor(...args) { super(...args); } が自動であるイメージ
}

const d = new Dog("ポチ");
console.log(d.name); // ポチ
JavaScript

constructor を書くのは「親の初期化+自分独自の初期化」をしたいときだけ、と考えてください。


親のメソッドを使う・上書きする(オーバーライド)

親クラスのメソッドをそのまま使う

子クラスは、何もせずとも親クラスのメソッドをそのまま使えます。

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} が何かを喋った`);
  }
}

class Cat extends Animal {
  meow() {
    console.log(`${this.name}「ニャー」`);
  }
}

const c = new Cat("タマ");
c.speak(); // 親クラスの speak
c.meow();  // Cat 独自
JavaScript

「共通の動き」は親クラスに、「種類ごとの動き」は子クラスに書くイメージです。

親のメソッドを「書き換える」(オーバーライド)

子クラスで、親と同じ名前のメソッドを定義すると、親のメソッドを上書き(オーバーライド)できます。

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} が何かを喋った`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name}「ワン!」`);
  }
}

const d = new Dog("ポチ");
d.speak(); // ポチ「ワン!」(親ではなく子の speak が呼ばれる)
JavaScript

さらに、親の処理も呼びたい場合は super.メソッド名() を使います。

class Dog extends Animal {
  speak() {
    super.speak(); // Animal の speak を呼ぶ
    console.log(`${this.name} はしっぽを振っている`);
  }
}

const d = new Dog("ポチ");
d.speak();
// ポチ が何かを喋った
// ポチ はしっぽを振っている
JavaScript

ここが重要です。
「基本の動き+α」にしたいときは、
子のメソッド内で super.xxx() を呼ぶことで、親の動きの上に処理を積み増しできます。


継承を使うと何が嬉しいのか(設計の視点)

共通部分を一箇所にまとめられる

例えばゲームのキャラクターを考えます。

すべてのキャラが持つ共通のもの(名前、HP、ダメージ処理など)は親クラスにまとめ、

class Character {
  constructor(name, hp) {
    this.name = name;
    this.hp = hp;
  }

  damage(amount) {
    this.hp = Math.max(0, this.hp - amount);
  }

  show() {
    console.log(`${this.name} HP: ${this.hp}`);
  }
}
JavaScript

そこから「勇者」「魔法使い」を継承します。

class Hero extends Character {
  attack() {
    console.log(`${this.name} のこうげき!`);
  }
}

class Mage extends Character {
  castSpell() {
    console.log(`${this.name} は魔法をとなえた!`);
  }
}

const h = new Hero("勇者", 120);
const m = new Mage("魔法使い", 80);

h.damage(30);
m.damage(50);

h.show(); // 勇者 HP: 90
m.show(); // 魔法使い HP: 30

h.attack();
m.castSpell();
JavaScript

ここが重要です。
「全キャラに共通する処理を Character に1回だけ書き、種類ごとの違いだけを子クラスに書く」。
こうしておけば、共通仕様を変えるときにも親クラスだけ触れば済みます。

「〜の一種」を自然に表現できる

DogAnimal の一種、MageCharacter の一種、といった「is-a 関係」があるとき、
継承は非常に自然なモデルになります。

d instanceof Animaltrue であるように、
Dog のインスタンスは「Animal でもある」ので、
「Animal を受け取る関数」に Dog を渡しても問題ありません。


継承と static メソッド・プロパティ

static メソッドも継承される

親クラスの static メソッドも子クラスから呼び出せます。

class Animal {
  static isAnimal(obj) {
    return obj instanceof Animal;
  }
}

class Dog extends Animal {}

const d = new Dog("ポチ");

console.log(Animal.isAnimal(d)); // true
console.log(Dog.isAnimal(d));    // true(Dog からも使える)
JavaScript

必要であれば、static メソッドも子クラスでオーバーライドできます。
ただし、初心者のうちはまずインスタンスメソッドの継承・オーバーライドが押さえられていれば十分です。


継承の注意点と、やりすぎないコツ(重要)

継承しないほうがシンプルな場合も多い

便利な継承ですが、むやみに使うとクラス間の関係が複雑になりがちです。
「何でもかんでも継承」ではなく、次のようなときにだけ使うと良いです。

「明らかに“〜の一種”と言える関係がある」
「共通のプロパティ・メソッドが多く、コピペせずまとめたい」
「子クラスを親クラスとして扱う場面が自然に想像できる(Animal として扱う Dog など)」

逆に、「たまたまプロパティが似ているだけ」のものを継承でつなぐと、
後で仕様がズレたときに苦しくなります。

継承はあくまで「強い関係」です。
迷ったときは、まず普通に別クラスとして定義し、共通処理だけ関数に切り出す、という方法も検討してみてください。


例題で継承の感覚を固める

例1:図形クラスの継承

class Shape {
  area() {
    return 0;
  }

  describe() {
    console.log(`面積: ${this.area()}`);
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius * this.radius;
  }
}

const r = new Rectangle(10, 5);
const c = new Circle(3);

r.describe(); // 面積: 50
c.describe(); // 面積: 28.274...
JavaScript

describe() は親クラスに1つだけ定義しておき、具体的な area() の計算を子クラスでオーバーライドしています。

例2:ログインユーザーと管理者ユーザー

class User {
  constructor(name) {
    this.name = name;
  }

  describe() {
    console.log(`${this.name}(一般ユーザー)`);
  }
}

class Admin extends User {
  constructor(name, permissions = []) {
    super(name);
    this.permissions = permissions;
  }

  describe() {
    console.log(`${this.name}(管理者)権限: ${this.permissions.join(", ")}`);
  }

  hasPermission(name) {
    return this.permissions.includes(name);
  }
}

const u = new User("Alice");
const a = new Admin("Bob", ["READ", "WRITE"]);

u.describe();
a.describe();
console.log(a.hasPermission("WRITE")); // true
JavaScript

「基本的なユーザー」と「権限付きの管理者」という「〜の一種」関係が、継承で自然に表現できています。


まとめ

継承(extends)の核心は、
「共通部分を持つクラスを親として定義し、それをベースにした“少し違うバージョン”を子クラスとして定義する」 ことです。

押さえておきたいポイントは次のとおりです。

親クラスを class Parent { ... }、子クラスを class Child extends Parent { ... } と書く。
子クラスで constructor を定義するなら、最初に super(...) を呼ぶ。
子クラスは親クラスのプロパティ・メソッドをそのまま使える。
親と同名のメソッドを子クラスで定義するとオーバーライドになり、super.method() で親の実装も呼べる。
「〜の一種」という関係があるときにだけ継承を使うと、設計がすっきりする。

まずは「共通部分がはっきりしている2〜3種類のクラス」を考えて、
親クラスと子クラスに分けてみるところから練習してみてください。
継承がハマる場面で使うと、「あ、このために class と extends があったのか」と腑に落ちるはずです。

タイトルとURLをコピーしました