はじめに

JavaScript のクラス構文( class )は ES2015 で導入されましたが、その後も改良が続き、現在では private フィールド や static フィールド など、「本物のクラスらしい機能」が揃っています。

本章では、次のポイントを中心に、モダンなクラスの使い方 を整理します。

  • constructor(初期化処理)
  • メソッド(プロトタイプに登録される)
  • private フィールド(インスタンスの内部状態)
  • static フィールド / メソッド(クラス共通の状態)
  • setter / getter メソッド
  • クラス継承
  • インスタンス生成

クラス構文の基本

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

  sayHello() {
    console.log(`Hello, ${this.name}`);
  }
}

const u = new User("Kon");
u.sayHello();

ポイント:

  • constructor はインスタンス生成時に自動で呼ばれる。
  • メソッドは function を書かずに定義できる。
  • メソッドは プロトタイプに登録される。(メモリ効率が良い)

constructor の役割

constructor は インスタンスの初期化処理を書く場所 です。

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

constructor の注意点

  • return を書かない。

→constructor に return を書くと、プリミティブを返しても無視され、オブジェクトを返した場合のみ 返り値が置き換わるという意図しない挙動になります。

  • 非同期処理は基本的に書かない。

→ インスタンス生成が「途中で止まる」状態を作りやすいため


メソッドの定義

クラス内で定義したメソッドは、すべて プロトタイプメソッド になります。

class User {
  sayHello() {
    console.log("Hello");
  }
}

なぜプロトタイプに登録されるのか?

  • 全インスタンスで共有されるためメモリ効率が良い
  • JavaScript のオブジェクトモデルに沿っている

アロー関数をメソッドに使うべきか?

class User {
  sayHello = () => { ... } // ← これは「インスタンスごとに生成される」
}

👉 アロー関数は「メソッド」ではなく「インスタンスプロパティ」である。

理由 内容
プロトタイプに登録されない インスタンス毎に関数が生成される
this が固定されすぎる 柔軟性が失われ、再利用性が下がる
メソッドとして扱えない 継承・super・プロトタイプチェーンの恩恵を受けられない

this の束縛は安定するが、メモリ効率が悪くなるため、通常のメソッドで十分です。


private フィールド(#field

以前の JavaScript では、private を実現するにはクロージャを使う必要がありました。しかし、クロージャはメモリ効率が悪く、プロトタイプメソッドから参照できないという欠点がありました。

ES2022 の private フィールドは、インスタンスの内部状態を隠すための構文として、これを正式に解決した仕組みです。

class Counter {
  #count = 0;

  increment() {
    this.#count++;
  }

  get value() {
    return this.#count;
  }
}

特徴

  • #name の形式で宣言する。
  • インスタンス毎に存在する。
  • 外部からアクセスできない。
  • クラス内のどのメソッドからも参照できる。

従来、プロトタイプ型のクラスで用いられてきた let での定義は、class 直下では行えません。その為、 private の代わりにはなりません。

よくある誤解:

class User {
  
  let secret = 123; // ❌ エラー
  
  constructor() {
    secret = 123;    // ← これは constructor のローカル変数
  }

  show() {
    console.log(secret); // ❌ エラー
  }
}

👉 class の直下に書けるのは「フィールド宣言」だけであり、let / const / var は書けない(文法エラー)。

つまり、クラス変数としてもインスタンス変数としても使えない。

class User {
  constructor() {
    let secret = 123; // ← これは constructor のローカル変数
  }

  show() {
    console.log(secret); // ❌ エラー
  }
}

👉 let で定義された変数は、 constructor の中だけで存在するローカル変数です。インスタンスに保存されない為、他のメソッドから参照できません。

つまり、

  • let / const は “関数スコープ”
  • private は “インスタンススコープ”

という決定的な違いがあります。

注意すること:

private フィールドは、 class 直下で宣言が必須となります。

class User {
  #secret; // ← 必ず必要

  constructor() {
    this.#secret = 123; // OK
  }
}

宣言しないと構文エラーになります。👇

class User {
  constructor() {
    this.#secret = 123; // ❌ エラー:#secret が宣言されていない
  }
}

👉 private は構文レベルで管理される特別なフィールド だからです。

一方、public は宣言不要(constructor だけで作れる)です。

class User {
  constructor() {
    this.name = "Kon"; // OK(public)
  }
}

👇 public フィールドは、ただのオブジェクトのプロパティだからです。動的言語の JavaScript では、プロパティはいつでも追加できます。


private メソッド

内部処理を隠したい場合(外部に公開したくない場合)は private メソッドを使います。

class PasswordValidator {
  validate(pw) {
    return this.#checkLength(pw) && this.#checkSymbol(pw);
  }

  #checkLength(pw) { return pw.length >= 8; }
  #checkSymbol(pw) { return /[!@#$%^&*]/.test(pw); }
}

👉 外部に公開したいのは validate() だけ、内部の実装は自由に変更できるように private にします。


static フィールド

クラス変数を定義したい場合は、 static フィールドを使用します。

class User {
  static count = 0;

  constructor() {
    User.count++;
  }
}

console.log(User.count); // 1

特徴

  • クラスに属する変数
  • 全インスタンスで共有される。
  • アクセスは User.count のようにクラス名を用いて行う。

よくある誤解:

static フィールドは クラス自身のプロパティ です。インスタンスからはアクセスできません。

class User {
  static count = 0;

  constructor() {
    User.count++;
  }
}

const u = new User();
u.count; // undefined

注意すること:

static フィールドは、class 直下で宣言する必要があります。

class User {
  static secret; // ← static フィールドとして宣言

  constructor() {
    User.secret = 123; // OK(static フィールドへの代入)
  }
}

class 直下で、static フィールドを宣言しない場合、エラーにはなりませんが、それは static フィールドではなく、単なるクラスの動的プロパティになります。

class User {
  constructor() {
    User.secret = 123; // ← 動的プロパティの追加(static フィールドではない)
  }
}

※ 見た目は似ていますが、static フィールドと動的プロパティは別物 です。

class User1 {
  static secret; // ← static フィールドとして宣言

  constructor() {
    User1.secret = 123; // OK(static フィールドへの代入)
  }
}

class User2 {
  constructor() {
    User2.secret = 123; // ← 動的プロパティの追加(static フィールドではない)
  }
}

console.log(Object.hasOwn(User1, "secret")); // true
console.log(Object.hasOwn(User2, "secret")); // false

👉 インスタンス生成後は、constructor の実行により、同じようにみえてしまう為、非常にみつけづらいバグになる可能性があります。


static メソッド

クラスメソッドを定義したい場合は、static メソッドを使用します。

class MathUtil {
  static clamp(value, min, max) {
    return Math.min(max, Math.max(min, value));
  }
}

MathUtil.clamp(10, 0, 5); // → 5

用途:

  • ユーティリティ関数
  • インスタンスを数える
  • 設定値を保持する
  • ID 発行
  • バリデーション
  • インスタンス生成の補助(factoryメソッドなど)

(つづく)