constructor

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2017.

constructor メソッドは、クラスで作成されたオブジェクトインスタンスの生成と初期化を行うための特殊なメソッドです。

メモ: このページでは constructor の構文を紹介します。すべてのオブジェクトに存在する constructor プロパティについては、 Object.prototype.constructor を参照してください。

試してみましょう

構文

js
constructor() { /* … */ }
constructor(argument0) { /* … */ }
constructor(argument0, argument1) { /* … */ }
constructor(argument0, argument1, /* …, */ argumentN) { /* … */ }

追加の構文上の制約があります。

解説

コンストラクターを使用すると、インスタンス化されたオブジェクトに対して、他のメソッドを呼び出す前に行う必要のある独自の初期化を行うことができます。

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

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

const otto = new Person("オットー");

otto.introduce(); // こんにちは、私はオットーです。

独自のコンストラクターを提供しなかった場合は、既定のコンストラクターが提供されます。クラスが基底クラスである場合、既定のコンストラクターは空です。

js
constructor() {}

クラスが派生クラスの場合、既定のコンストラクターが親コンストラクターを呼び出し、与えられた引数を渡します。

js
constructor(...args) {
  super(...args);
}

メモ: 上記のような明示的なコンストラクターと既定のコンストラクターの異なる点は、後者が実際には配列イテレーターを呼び出して引数のスプレッドを行わないことです。

それがこのようなコードを動作させることができます。

js
class ValidationError extends Error {
  printCustomerMessage() {
    return `Validation failed :-( (details: ${this.message})`;
  }
}

try {
  throw new ValidationError("Not a valid phone number");
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(error.name); // これは ValidationError の代わりのエラー
    console.log(error.printCustomerMessage());
  } else {
    console.log("Unknown error", error);
    throw error;
  }
}

ValidationError クラスは、独自の初期化を行う必要がないため、明示的なコンストラクターは必要ありません。 既定のコンストラクターは、与えられた引数から親の Error の初期化を行います。

ただし、独自のコンストラクターを提供し、クラスが親クラスから派生している場合は、 super() を使用して親クラスのコンストラクターを明示的に呼び出す必要があります。 例えば、以下のようになります。

js
class ValidationError extends Error {
  constructor(message) {
    super(message); // 親クラスのコンストラクターの呼び出し
    this.name = "ValidationError";
    this.code = "42";
  }

  printCustomerMessage() {
    return `検証に失敗しました :-( (details: ${this.message}, code: ${this.code})`;
  }
}

try {
  throw new ValidationError("正しい電話番号ではありません。");
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(error.name); // これは ValidationError になる
    console.log(error.printCustomerMessage());
  } else {
    console.log("未知のエラーです", error);
    throw error;
  }
}

クラスで new を使用すると、以下の段階を踏みます。

  1. (派生クラスの場合) super() 呼び出しが評価される前の constructor 本体。この部分はまだ初期化されていないので、 this にアクセスしてはいけません。
  2. (派生クラスの場合) super() 呼び出しが評価され、同じ処理で親クラスが初期化されます。
  3. 現在のクラスのフィールドが初期化されます。
  4. super() 呼び出し後の constructor 本体(基底クラスの場合は本体全体)が評価されます。

constructor 本体の中では、 this で作成されるオブジェクトにアクセスしたり new で呼び出されるクラスに new.target でアクセスしたりすることができます。メソッド(ゲッターセッターを含む)とプロトタイプチェーンconstructor が実行される前に this で初期化されているので、スーパークラスのコンストラクターからサブクラスのメソッドにアクセスすることもできることに注意してください。しかし、これらのメソッドが this を使用している場合、 this はまだ完全に初期化されていません。これは、派生クラスのパブリックフィールドを読むと undefined になり、プライベートフィールドを読むと TypeError になるということです。

js
new (class C extends class B {
  constructor() {
    console.log(this.foo());
  }
} {
  #a = 1;
  foo() {
    return this.#a; // TypeError: Cannot read private member #a from an object whose class did not declare it
    // これは、クラスが宣言していないのではなく、スーパークラスの
    // コンストラクターが実行されている時点で、プライベートフィールドが
    // まだ初期化されていないため。
  }
})();

constructor メソッドは返値を持つことができます。基底クラスはコンストラクターから何らかの値を返すことができますが、派生クラスはオブジェクトまたは undefined を返すか、 TypeError を発生させなければなりません。

js
class ParentClass {
  constructor() {
    return 1;
  }
}

console.log(new ParentClass()); // ParentClass {}
// 返値はオブジェクトではないので無視される。 これはコンストラクター関数と同じ。

class ChildClass extends ParentClass {
  constructor() {
    return 1;
  }
}

console.log(new ChildClass()); // TypeError: Derived constructors may only return object or undefined

親クラスのコンストラクターがオブジェクトを返した場合、そのオブジェクトは派生クラスのクラスフィールドを定義する際の値として使用します。このトリックは「返値の上書き」と呼ばれ、派生クラスのフィールド(プライベートなものも含む)を無関係なオブジェクトに定義することができます。

constructor は通常のメソッド構文に従うので、デフォルト引数残余引数などをすべて使用することができます。

js
class Person {
  constructor(name = "名無し") {
    this.name = name;
  }
  introduce() {
    console.log(`こんにちは、私は${this.name}`);
  }
}

const person = new Person();
person.introduce(); // こんにちは、私は名無し

コンストラクターはリテラル名でなければなりません。計算プロパティ名はコンストラクターにはなれません。

js
class Foo {
  // これは計算プロパティ名です。コンストラクターとしてピックアップされることはありません。
  ["constructor"]() {
    console.log("called");
    this.a = 1;
  }
}

const foo = new Foo(); // ログ出力なし
console.log(foo); // Foo {}
foo.constructor(); // "called" と出力
console.log(foo); // Foo { a: 1 }

非同期メソッド、ジェネレーターメソッド、アクセサ、クラスフィールドは constructor と名付けることは禁止されています。プライベートな名前を #constructor と呼び出すことはできません。 constructor という名前のメンバーはプレーンなメソッドでなければなりません。

constructor メソッドの使用

このコードスニペットは、classes sample (ライブデモ) から転載しています。

js
class Square extends Polygon {
  constructor(length) {
    // ここでは、ポリゴンの幅と高さを指定された長さにして、親クラスの
    // コンストラクターを呼び出しています。
    super(length, length);
    // メモ: 派生クラスでは、`this` を使用する前に `super()` を呼び出す
    // 必要があります。これを省略すると ReferenceError が発生します。
    this.name = "Square";
  }

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

  set area(value) {
    this.height = value ** 0.5;
    this.width = value ** 0.5;
  }
}

異なるプロトタイプにバインドされたコンストラクターでの super を呼び出し

super() は現在のクラスのプロトタイプであるコンストラクターを呼び出します。現在のクラスのプロトタイプを変更した場合、 super() は新しいプロトタイプのコンストラクターを呼び出します。現在のクラスの prototype プロパティを変更しても、 super() が呼び出すコンストラクターには影響しません。

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

class Rectangle {
  constructor() {
    this.name = "Rectangle";
  }
}

class Square extends Polygon {
  constructor() {
    super();
  }
}

// Polygon の代わりに(基本クラスである) Rectangle を継承するようにする
Object.setPrototypeOf(Square, Rectangle);

const newInstance = new Square();

// newInstance はまだ Polygon のインスタンスです。
// Square.prototype のプロトタイプを変更していないので、
// newInstance のプロトタイプチェーンは以下のままです。
//   newInstance --> Square.prototype --> Polygon.prototype
console.log(newInstance instanceof Polygon); // true
console.log(newInstance instanceof Rectangle); // false

// ただし、 super() はコンストラクターとして Rectangle を呼び出すため、
// newInstance の name プロパティは Rectangle のロジックで初期化されます。
console.log(newInstance.name); // Rectangle

仕様書

Specification
ECMAScript Language Specification
# sec-static-semantics-constructormethod

ブラウザーの互換性

BCD tables only load in the browser

関連情報