コンストラクタは、クラスからインスタンスを生成する際、newキーワードとともに一度だけ自動的に呼び出されるメソッドです。
主に、オブジェクトが持つデータ(プロパティ)の初期値を設定する役割を担います。
TypeScriptにおけるconstructorは、単なる初期化メソッドにとどまりません。
型推論、アクセス修飾子(public/privateなど)を用いたカプセル化、コード量を減らす「省略記法」など、JavaScriptにはない数多くの機能が備わっています。
constructorの基本構文と役割
TypeScriptでコンストラクタを定義する基本的な方法を見ていきましょう。
- プロパティの初期化と型定義
- JavaScript(ES6)のコンストラクタとの違い
プロパティの初期化と型定義
TypeScriptでクラスを定義する場合、クラスのトップレベルでプロパティの「名前」と「型」を宣言し、constructorの中で実際の値を代入(初期化)します。
class User {
// 1. プロパティの型宣言
name: string;
age: number;
// 2. constructorによる初期化
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`こんにちは、${this.name}です。${this.age}歳です。`);
}
}
// インスタンスの生成(ここでconstructorが実行される)
const user1 = new User("田中", 25);
user1.greet();JavaScript(ES6)のコンストラクタとの違い
素のJavaScriptでは、クラスのトップレベルでプロパティを宣言しなくても、constructorの中でthis.name = name;と書くことができました。
しかし、TypeScriptは「事前のプロパティ宣言」が必須です。(※後述する省略記法を除く。)
宣言せずにthis.nameにアクセスすると、「プロパティ’name’は型’User’に存在しません」というコンパイルエラーが発生します。
これにより、クラスがどのようなデータを持っているのかが可視化され、堅牢な設計が保証されます。
引数プロパティによる省略記法
基本構文を見て、「プロパティ名(nameやage)を何度も書くのが面倒くさい」と感じた方も多いでしょう。
TypeScriptは、冗長なコードを短縮する「引数プロパティ(Parameter Properties)」という機能が備わっています。
- 冗長なコードを1行にまとめる構文
readonly修飾子との組み合わせ
冗長なコードを1行にまとめる構文
コンストラクタの引数にアクセス修飾子(public,private,protected)を付けることで、プロパティの宣言と初期化の代入処理を省略できます。
class Product {
// プロパティの事前宣言も、this.xxx = xxx の代入も不要!
constructor(
public id: string,
public name: string,
private price: number
) {}
showDetails() {
console.log(`[${this.id}] ${this.name} - ¥${this.price}`);
}
}
const item = new Product("A001", "ノートPC", 150000);
console.log(item.name); // アクセス可能
// console.log(item.price); // privateなので外部からはアクセス不可(エラー)AngularやNestJSといったTypeScriptベースのフレームワークにおいて、DI(依存性の注入)を行う際の記法として登場します。
実務では必須のテクニックです。
readonly修飾子との組み合わせ
初期化された後に値を変更されたくないプロパティには、readonly修飾子を組み合わせるのがベストプラクティスです。
class Config {
constructor(public readonly apiKey: string) {}
}
const appConfig = new Config("secret-key-123");
console.log(appConfig.apiKey); // 読み取りはOK
// appConfig.apiKey = "new-key"; // readonlyなので代入不可(エラー)クラスの継承extendsとsuper()の使い方
オブジェクト指向の醍醐味である「継承」を行う際、コンストラクタの扱いにはルールが存在します。
- 親クラスのコンストラクタを呼び出す
thisにアクセスする前のルール
親クラスのコンストラクタを呼び出す
子クラス(派生クラス)で独自のconstructorを定義する場合、内部で親クラスのコンストラクタであるsuper()を呼び出す必要があります。
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
constructor(name: string, public breed: string) {
// 親クラス(Animal)のコンストラクタにnameを渡す
super(name);
}
bark() {
console.log(`${this.name} (${this.breed}) が吠えました!`);
}
}
const myDog = new Dog("ポチ", "柴犬");
myDog.bark();thisにアクセスする前のルール
TypeScriptにおけるルールとして、「子クラスのコンストラクタ内ではsuper()を呼び出す前にthisにアクセスしてはならない」という制約があります。
class Cat extends Animal {
constructor(name: string, public age: number) {
// console.log(this.name); // エラー! super() の前には this は使えない
super(name);
console.log(`${this.name}が生まれました。`); // super() の後ならOK
}
}親クラスの初期化が完了する前に、子クラスから未完成のオブジェクト(this)を操作して不具合を起こすのを防ぐ言語仕様です。
constructor特有の機能:オーバーロード
TypeScriptは、関数だけでなくコンストラクタのオーバーロード(引数のパターンを複数定義すること)も可能です。
これにより、異なる引数の組み合わせでインスタンスを生成できます。
- 複数の初期化パターンを定義する
- 実装側の型ガード
複数の初期化パターンを定義する
「名前と年齢を渡して作成するパターン」と、「名前だけ渡して年齢はデフォルト値にするパターン」を定義してみましょう。
class Person {
name: string;
age: number;
// 1. シグネチャの宣言(型のみ定義)
constructor(name: string);
constructor(name: string, age: number);
// 2. 実際の実装(すべてのパターンを受け入れられるようにする)
constructor(name: string, age?: number) {
this.name = name;
this.age = age !== undefined ? age : 20; // 年齢がなければ20歳にする
}
}
const p1 = new Person("田中"); // ageは20になる
const p2 = new Person("佐藤", 30); // ageは30になる実装側の型ガード
オーバーロードの実装側(実際の処理を書くコンストラクタ)は一つしか書けません。
そのため、オプショナル引数(?)やユニオン型(|)を用いて全てのシグネチャを網羅し、内部でtypeofなど用いて型ガード(条件分岐)を行うのがTypeScript流の実装方法です。
アクセス修飾子とprivate constructorの活用
コンストラクタにprivate修飾子をつけるというテクニックがあります。
「外からnewできないクラス」をあえて作ることで実現できるデザインパターンを紹介します。
newを禁止するprivate constructor- シングルトンパターンの実装例
newを禁止するprivate constructor
クラスは外部からnewされて意味を持ちますが、データベースの接続管理やアプリケーション全体の設定管理など、「システム全体で1つしかインスタンスを作りたくない」場合があります。
このような要件を満たすのが「シングルトン(Singleton)パターン」です。
シングルトンパターンの実装例
コンストラクタをprivateにし、クラス内部のstaticメソッド経由でしかインスタンスを取得できないように制御します。
class DatabaseConnection {
// 唯一のインスタンスを保持する静的プロパティ
private static instance: DatabaseConnection;
// 外部からの new を禁止する!!
private constructor() {
console.log("データベースに接続しました...");
}
// インスタンスを取得するための静的メソッド
public static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
// まだインスタンスがなければ、ここで初めて作成する
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
public query(sql: string) {
console.log(`クエリ実行: ${sql}`);
}
}
// const db = new DatabaseConnection(); // エラー:constructorはprivateです
// 正しい取得方法
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();
console.log(db1 === db2); // true (全く同じインスタンスを指している)typescript constructorのアクセス権をコントロールすることで、オブジェクトの生成ライフサイクルを掌握することが可能になります。

