JavaScript | ES6+ 文法:クラス構文 – constructor

JavaScript JavaScript
スポンサーリンク

constructor とは何か(まずはイメージから)

constructor は、class から new したときに「最初に一回だけ」呼ばれる特別なメソッドです。
役割はシンプルで、

  • インスタンスが生まれた瞬間に
  • そのインスタンスが持つべき初期データ(プロパティ)をセットする

ための場所です。

class User {
  constructor(name) {
    this.name = name;      // インスタンスごとのデータ
  }

  greet() {
    console.log(`こんにちは、${this.name} です`);
  }
}

const u = new User("Alice"); // ここで constructor が呼ばれる
u.greet();                   // こんにちは、Alice です
JavaScript

new User("Alice") と書いたときに、constructor(name) が呼ばれ、
その中で this.name = name としている」というイメージを持ってください。
ここが重要です:constructor の中で this に何を持たせるかが、そのクラスの“型”を決めると思っていいです。

constructor と this の関係(ここはしっかり理解してほしい)

this は「今まさに生成中のインスタンス」

constructor の中で出てくる this は、そのとき new されているインスタンスそのものを指します。

class Counter {
  constructor(initialValue = 0) {
    this.value = initialValue;
  }

  increment() {
    this.value++;
  }

  show() {
    console.log(this.value);
  }
}

const c1 = new Counter(10);
const c2 = new Counter(100);

c1.increment();
c1.show(); // 11

c2.increment();
c2.show(); // 101
JavaScript

c1 を作るときの constructor の中では this === c1
c2 を作るときは this === c2 です。

この「インスタンスごとに別々の this」があるからこそ、
同じクラスから作ったインスタンスでも、違うデータを持てます。

ここが重要です:
constructor の中で this.xxx = ... と書くたびに、「インスタンスの属性(プロパティ)」が決まっていく
「このクラスのインスタンスは何を持つのか?」を、constructor で設計する感覚です。

constructor がなくてもクラスは動く(暗黙のデフォルト)

class の中に constructor を書かなかった場合、
「何もしない空の constructor」が自動的に用意されます。

class Empty {
}

const e = new Empty(); // エラーにならない(暗黙の constructor がある)
JavaScript

つまり、

class Empty {
}
JavaScript

は、実質こう書いたのと同じです。

class Empty {
  constructor() {}
}
JavaScript

ただし、自分で初期化したいプロパティが出てきたら、
自分で constructor を定義する必要があります。

引数とデフォルト値(使い勝手を決める大事な部分)

引数で「生成に必要な情報」を受け取る

constructor の引数は、そのクラスのインスタンスを作るために「外から渡してほしい情報」です。

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

  describe() {
    console.log(`${this.id}: ${this.name} (${this.role})`);
  }
}

const u = new User(1, "Alice", "admin");
u.describe(); // 1: Alice (admin)
JavaScript

何を引数にするかを考えるのは、「このクラスをどう使わせたいか」という設計の中心になります。

デフォルト引数で「使い方を優しくする」

ES6 の関数と同じように、constructor でもデフォルト引数が使えます。

class User {
  constructor(name, role = "user") {
    this.name = name;
    this.role = role;
  }

  describe() {
    console.log(`${this.name} (${this.role})`);
  }
}

const u1 = new User("Alice");
const u2 = new User("Bob", "admin");

u1.describe(); // Alice (user)
u2.describe(); // Bob (admin)
JavaScript

ここが重要です:
「よくあるケース」は引数を省略できるようにしておくと、クラスが使いやすくなる
constructor の引数とデフォルト値は、「このクラスの使い勝手」を決めるインターフェースだと考えると良いです。

「constructor は1つだけ」「オーバーロードはない」

同じ class に constructor を2つは書けない

他の言語(Java, C# など)には「コンストラクタのオーバーロード」という概念がありますが、
JavaScript の class は constructor を1つしか持てません

class User {
  // これは NG
  // constructor(name) { ... }
  // constructor(id, name) { ... }
}
JavaScript

こういうことはできないので、「引数のパターン」を自前で解釈する必要があります。

パラメータの解釈で柔軟にする

例えば、「文字列1つなら name として扱い、オブジェクトなら id と name を取る」
というような実装も書けます。

class User {
  constructor(arg) {
    if (typeof arg === "string") {
      this.id = null;
      this.name = arg;
    } else if (typeof arg === "object") {
      this.id = arg.id ?? null;
      this.name = arg.name ?? "no-name";
    }
  }

  describe() {
    console.log(`${this.id}: ${this.name}`);
  }
}

const u1 = new User("Alice");
const u2 = new User({ id: 1, name: "Bob" });

u1.describe(); // null: Alice
u2.describe(); // 1: Bob
JavaScript

ここが重要です:
constructor は1つしか書けないので、「引数の形を見て中で振り分ける」という設計をする
ただし、複雑にしすぎると読みづらくなるので、
初心者のうちは「素直な引数にしておく」ほうが分かりやすいことが多いです。

継承時の constructor と super(ここはつまずきやすい)

子クラスに constructor を書くときは super が必須

extends を使って継承する場合、
子クラスで constructor を定義するなら、必ず最初に super(...) を呼ばなければなりません

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

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

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

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

もし super(name) を書かずに this.breed = ... といった操作をしようとすると、エラーになります。

class BadDog extends Animal {
  constructor(name, breed) {
    // super(name); を忘れると…
    this.breed = breed; // ここでエラー
  }
}
JavaScript

ここが重要です:
子クラスの constructor では、「親の初期化」→「子の初期化」の順番が必須
constructor を書いたら、「まず super(...)」と体で覚えるくらいでちょうどいいです。

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

子クラス独自の初期化が不要で、
親の constructor に引数をそのまま渡せばいいだけなら、子クラスに constructor を書かなくてもOKです。

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 は「必要になったら追加する」ものであって、
無理に子クラスに書かなくてもよい、ということも覚えておいてください。

constructor とクラスフィールド/メソッドの役割分担

constructor では「初期データ」だけに集中する

class の中には、いろいろ書けますが、役割を分けて考えると整理しやすくなります。

  • constructor:インスタンスの初期状態を決める(this にプロパティをセット)
  • インスタンスメソッド:インスタンスに対する操作
  • static メソッド:クラスに対するユーティリティ的な操作

例えばユーザークラスなら、こんな感じです。

class User {
  constructor(id, name) {
    this.id = id;
    this.name = name;
    this.active = true;
  }

  deactivate() {
    this.active = false;
  }

  describe() {
    console.log(`${this.id}: ${this.name} (${this.active ? "有効" : "無効"})`);
  }

  static createGuest() {
    return new User(0, "Guest");
  }
}
JavaScript

ここが重要です:
constructor の中で「ロジック(計算)」をやりすぎない
できるだけ、「初期値を決める場所」としてシンプルに保ったほうが、クラス全体の見通しが良くなります。

よくある勘違いと注意点(重要な落とし穴)

constructor を普通のメソッドのように呼べない

constructor は特別なメソッドで、自分で直接呼ぶことはできません

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

const u = new User("Alice"); // OK(constructor が裏で呼ばれる)

// u.constructor("Bob"); // こういう使い方はしない
JavaScript

「作成済みのインスタンスを再初期化したい」という場合は、
constructor を呼び直すのではなく、別のメソッド(resetinitFrom など)を自分で用意するべきです。

this を使う前に super を呼ぶ必要がある(子クラス)

さきほど触れましたが、子クラスの constructor では super() より前に this を触るとエラーになります。
これは JavaScript のクラス仕様上のルールです。

class Parent {
  constructor() {
    this.value = 1;
  }
}

class Child extends Parent {
  constructor() {
    // this.value = 2; // ここで触るとエラー
    super();
    this.value = 2;  // これは OK
  }
}
JavaScript

extends しているクラスに constructor を書いたら、まず super」と、
条件反射で書けるようにしてしまうのが一番楽です。

例題で理解を固める

例1:シンプルなクラスと constructor

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  distanceFromOrigin() {
    return Math.sqrt(this.x ** 2 + this.y ** 2);
  }
}

const p = new Point(3, 4);
console.log(p.distanceFromOrigin()); // 5
JavaScript

例2:デフォルト値とオプション的な引数

class Button {
  constructor(label, color = "blue") {
    this.label = label;
    this.color = color;
  }

  describe() {
    console.log(`ボタン [${this.label}] 色: ${this.color}`);
  }
}

const b1 = new Button("OK");
const b2 = new Button("Cancel", "red");

b1.describe(); // ボタン [OK] 色: blue
b2.describe(); // ボタン [Cancel] 色: red
JavaScript

例3:継承と super

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

  greet() {
    console.log(`こんにちは、${this.name} です`);
  }
}

class Employee extends Person {
  constructor(name, department) {
    super(name);          // Person の constructor を呼ぶ
    this.department = department;
  }

  greet() {
    super.greet();        // 親の greet も使いつつ…
    console.log(`部署: ${this.department}`);
  }
}

const e = new Employee("Alice", "開発");
e.greet();
// こんにちは、Alice です
// 部署: 開発
JavaScript

例4:インスタンス生成用の static メソッドと組み合わせる

class Config {
  constructor(options) {
    this.theme = options.theme ?? "light";
    this.lang = options.lang ?? "ja";
  }

  static fromEnv(env) {
    return new Config({
      theme: env.THEME,
      lang: env.LANG
    });
  }
}

const env = { THEME: "dark", LANG: "en" };
const cfg = Config.fromEnv(env);
console.log(cfg.theme, cfg.lang); // dark en
JavaScript

まとめ

constructor の本質は、
「class からインスタンスを作るとき、そのインスタンスの最初の状態を決める場所」 です。

押さえておきたいポイントを整理すると、

  • constructor(...) { ... }new された瞬間に一度だけ呼ばれる
  • 中の this は「そのとき生成中のインスタンス」を指す
  • this.xxx = ... で「インスタンスが持つプロパティ(型)」を設計する
  • constructor は1つだけ。柔軟さが必要なら引数の解釈で工夫する
  • 継承時に子クラスで constructor を書くなら、必ず最初に super(...) を呼ぶ
  • constructor では「初期化」に集中させ、複雑な処理は通常メソッドや static に分離する

この感覚がつかめると、class 全体の理解も一気に楽になります。
小さなクラスをいくつか自分で作って、「どのプロパティを constructor で初期化するか」を意識しながら書いてみると、手に馴染んでいきます。

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