JavaScript の クラス について整理しておく。

JavaScript には class 構文があるがそれは単なるシンタックスシュガーであり、class を使わずともクラスを実現することができる。

ここで、改めてクラスとは。

クラス(コンピュータ)

オブジェクト指向プログラミングにおけるクラス(英: class)とは、オブジェクトを生成するための設計図あるいはひな形に相当するものである。

(中略)クラスには、クラス自身またはクラスのインスタンスが保持するデータと、データに関連したオブジェクトの振る舞いを記述できる。

クラス (コンピュータ) - Wikipedia

端的に言えば、状態と振る舞いを持つデータ(オブジェクト/インスタンス)の生成元となるもの。以降で記述する各方法はそれぞれ違いはあれど、どれもクラスの動作を実現している。

1. クロージャ・エンクロージャ方式

JavaScript は第一級関数をサポートしており、関数を第一級オブジェクトとして扱うことができる関数指向を持った言語。

第一級オブジェクト

第一級オブジェクト(ファーストクラスオブジェクト、first-class object)は、あるプログラミング言語において、たとえば生成、代入、演算、(引数・戻り値としての)受け渡しといったその言語における基本的な操作を制限なしに使用できる対象のことである。

第一級オブジェクト - Wikipedia

関数を返すという動作ができることを利用して、クロージャ(関数閉包)を作成できる。

1-1. 通常の関数での定義

function counter() {
  let count = 0;

  return function increment() {
    console.log((count += 1));
  };
}

const increment = counter();

increment(); // >> 1
increment(); // >> 2
increment(); // >> 3

ちなみに、外側の関数(今回の counter() {} )をエンクロージャ、内側の関数(今回の increment() {})をクロージャという。

1-2. アロー関数での定義

以下の通りアロー関数を使用しても同様。

const counter = () => {
  let count = 0;

  const increment = () => {
    console.log((count += 1));
  };

  return increment;
};

const increment = counter();

increment(); // >> 1
increment(); // >> 2
increment(); // >> 3

1-3. 通常の関数ならば new できる

function counter() {
  let count = 0;

  return function increment() {
    console.log((count += 1));
  };
}

const increment = new counter();

increment(); // >> 1
increment(); // >> 2
increment(); // >> 3

*アロー関数はコンストラクタとしては使用できないため、new できるのは通常の関数で定義した場合のみ。

2. 関数クラス方式

function Counter() {
  let count = 0;

  this.increment = () => {
    console.log((count += 1));
  };
}

const counter = new Counter();

counter.increment(); // >> 1
counter.increment(); // >> 2
counter.increment(); // >> 3

*アロー関数はコンストラクタとしては使用できないため、関数クラスのパターンはアロー関数では実現できない。

3. ファクトリークラス方式

3-1. 通常の関数での定義

function Counter() {
  let count = 0;

  return {
    increment() {
      console.log((count += 1));
    },
  };
}

const counter = Counter();

counter.increment(); // >> 1
counter.increment(); // >> 2
counter.increment(); // >> 3

3-2. アロー関数での定義

const Counter = () => {
  let count = 0;

  return {
    increment() {
      console.log((count += 1));
    },
  };
};

const counter = Counter();

counter.increment(); // >> 1
counter.increment(); // >> 2
counter.increment(); // >> 3

3-3. 通常の関数ならば new できる

function Counter() {
  let count = 0;

  return {
    increment() {
      console.log((count += 1));
    },
  };
}

const counter = new Counter();

counter.increment(); // >> 1
counter.increment(); // >> 2
counter.increment(); // >> 3

4. プロトタイプベースクラス方式

function Counter() {
  this._count = 0;
}

Counter.prototype.increment = function () {
  console.log((this._count += 1));
};

const counter = new Counter();

counter.increment(); // >> 1
counter.increment(); // >> 2
counter.increment(); // >> 3

*なおアロー関数では this の扱いが異なるため、以下ではうまくいかない。

function Counter() {
  this._count = 0;
}

Counter.prototype.increment = () => {
  console.log((this._count += 1));
};

const counter = new Counter();

counter.increment(); // >> NaN
counter.increment(); // >> NaN
counter.increment(); // >> NaN

5. class 構文方式

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

  increment() {
    console.log((this._count += 1));
  }
}

const counter = new Counter();

counter.increment(); // >> 1
counter.increment(); // >> 2
counter.increment(); // >> 3

*class 構文内のメソッドはアロー関数で定義しても問題ない。

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

  increment = () => {
    console.log((this._count += 1));
  };
}

const counter = new Counter();

counter.increment(); // >> 1
counter.increment(); // >> 2
counter.increment(); // >> 3

以上

色々あるが、基本的には以下の方針で使用すると良いのでは、というのが個人的な意見。

  • クロージャ・エンクロージャ方式
    • → 記述量がそれほど必要ない簡易的なクラスを表現したい時に使用する
  • 関数クラス方式
    • → 使用しない
  • ファクトリークラス方式
    • → 使用しない
  • プロトタイプベースクラス方式
    • → 使用しない
  • class 構文方式
    • → 基本的にこれを使用する

参考