JavaScriptのclassを理解する

classとは

JavaScriptのclassは、オブジェクト指向プログラミング(OOP)の概念を実現するための構文です。クラスは、同じプロパティやメソッドを持つオブジェクトの設計図となるものです。

classキーワードを使用してクラスを宣言し、そのクラス内でプロパティやメソッドを定義します。クラス内で定義されたメソッドは、そのクラスから作成されたオブジェクト(インスタンス)で使用することができます。

以下に、JavaScriptのclassの基本的な構文を示します。

class MyClass {
  constructor() {
    // コンストラクタ: インスタンスを作成する際に実行されるメソッド
    // インスタンス固有の初期化処理を行う
  }

  method1() {
    // メソッド1の定義
  }

  method2() {
    // メソッド2の定義
  }
}


上記の例では、MyClassというクラスを定義しています。constructorメソッドは、newキーワードを使用してインスタンスを作成する際に呼び出される特別なメソッドです。その他のメソッドは、クラスから作成されたインスタンスで呼び出すことができます。

クラスからインスタンスを作成するには、newキーワードを使用します。

const myObject = new MyClass();


myObjectMyClassのインスタンスであり、MyClassのメソッドを呼び出すことができます。

クラスは、コードの再利用性やメンテナンス性を向上させるための重要な機能です。また、クラスの継承やポリモーフィズムなどのOOPの概念を利用することもできます。

巻き上げ (ホイスティング)

JavaScriptのclass定義は、通常の関数宣言とは異なり、巻き上げ(ホイスティング)の動作が異なります。

巻き上げ(ホイスティング)とは、コードの実行前に関数や変数の宣言がスコープの先頭に移動する動作のことを指します。通常の関数宣言では、関数は巻き上げられて関数の宣言が行われる前に呼び出すことができます。

しかし、classの場合は、クラスの宣言自体は巻き上げられますが、クラスのメソッドは巻き上げられません。つまり、クラスを宣言する前にクラスのコンストラクタやメソッドを呼び出すことはできません。

以下に、classの巻き上げの例を示します。

const myObject = new MyClass();

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

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


上記の例では、MyClassのインスタンスを作成する前にnew MyClass()という行があります。通常の関数ならば、関数の宣言が巻き上げられるため、これは問題ありません。しかし、classの場合は、MyClassが宣言される前にMyClassのインスタンスを作成しようとすると、エラーが発生します。

classの巻き上げの挙動に注意する必要があります。クラスを使用する場合は、クラスの宣言よりも後ろでクラスのインスタンスを作成するようにしてください。

クラス式

クラス式は、名前を持つ場合と持たない場合の2つの形式で定義できます。

名前を持たないクラス式の場合、クラス自体には名前がありません。例えば、以下のように定義されるクラスは無名クラス式です。

let Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// 出力: "Rectangle"


無名クラス式の場合、Rectangleという変数に代入されたクラスオブジェクトを参照することができます。また、クラスオブジェクトのnameプロパティを通じて、クラスの名前にアクセスすることもできます。

一方、名前を持つクラス式の場合、クラス自体に名前があります。以下のように定義されるクラスは名前付きクラス式です。

let Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// 出力: "Rectangle2"


名前付きクラス式では、クラス内部でその名前を使用することができます。また、クラスオブジェクトのnameプロパティを通じて、クラスの名前にアクセスすることもできます。

名前付きクラス式は、クラス内で自己参照する場合やデバッグ情報などで使用されることがあります。一方、無名クラス式は、一度だけ使用する小さなクラスを定義する際などに便利です。

classで使用するconstructor()

constructor()は、JavaScriptのclass内で使用される特別なメソッドです。クラスから新しいインスタンス(オブジェクト)を作成する際に、newキーワードと共に自動的に呼び出されます。constructor()メソッドは、インスタンスの初期化処理を行うために使用されます。

以下に、constructor()メソッドの詳細と使い方を説明します。

  • constructor()メソッドの定義:
class MyClass {
  constructor(parameter1, parameter2) {
    // 初期化処理を記述
  }
}
  • メソッドを定義します。constructor()メソッドは、クラス内で1つだけ存在できます。
  • パラメータの受け取り: constructor()メソッドは、インスタンス作成時に渡される引数を受け取ることができます。引数は、クラスのインスタンスごとに異なる値を指定するために使用されます。引数はクラスの初期化に必要なデータを提供します。
  • インスタンス変数の初期化: constructor()メソッド内で、インスタンス変数(クラス内のプロパティ)を初期化することができます。インスタンス変数には、クラス内の他のメソッドからアクセスできます。通常、thisキーワードを使用してインスタンス変数を参照します。
  • 初期化処理の実行: constructor()メソッド内に、インスタンスの初期化に必要な処理を記述します。これには、インスタンス変数の初期化、他のオブジェクトとの関連付け、外部リソースの取得などが含まれます。

以下は、constructor()メソッドを使用した例です。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
    console.log("Personインスタンスが作成されました");
  }
}

const person1 = new Person("Alice", 25);
console.log(person1.name); // 出力: "Alice"
console.log(person1.age); // 出力: 25


上記の例では、Personクラスのconstructor()メソッドが定義されています。constructor()メソッドは2つの引数を受け取り、nameageというインスタンス変数を初期化します。console.log()文は、constructor()メソッドが呼び出されたことを示すために表示されます。

classを定義する際に必ずconstructorが必要か

classを定義する際には必ずしもconstructorを記述する必要はありません。constructorは、クラスがインスタンス化されるときにインスタンスの初期化を行うために使用される特別なメソッドです。

constructorが定義されていない場合、classはデフォルトの空のconstructorを持つことになります。この場合、インスタンス化されたオブジェクトは特別な初期化処理を持たず、classのプロパティやメソッドのみを利用できます。

以下はconstructorを省略した場合の例です:

class MyClass {
  // constructorが省略されている
  method() {
    // クラスのメソッド
  }
}


この場合、MyClassのインスタンスを作成するときには引数を指定せずに作成することができます。ただし、インスタンスが作成される際には自動的に空の初期化処理が実行されます。

constructorが不要な場合は、class内のプロパティやメソッドのみを使用する場合に適しています。一方、インスタンスの初期化が必要な場合や特定の設定を行いたい場合には、constructorを定義して初期化処理を記述する必要があります。

したがって、constructorclass定義内で必須ではなく、必要に応じて使用することができます。

constractorを使用しない方法

「constructor」を使用せずに、コードを書き直す例を以下に示します。

class Person {
  name;
  age;

  setName(name) {
    this.name = name;
  }

  setAge(age) {
    this.age = age;
  }

  introduce() {
    console.log(`My name is ${this.name} and I'm ${this.age} years old.`);
  }
}

const person1 = new Person();
person1.setName("Alice");
person1.setAge(25);
person1.introduce();


この例では、constructorを使用せずにインスタンス変数を直接クラスに定義し、メソッドを使用して値を設定します。setNameメソッドとsetAgeメソッドを使用して名前と年齢を設定し、introduceメソッドを使用して情報を出力します。

constructorを使用しない場合、インスタンス作成後にメソッドを呼び出して値を設定する必要があります。また、インスタンス変数は明示的に宣言する必要があります。

この方法では、初期化処理がconstructor内に集約されないため、コードの可読性や保守性が低下する可能性があります。constructorを使用することで、インスタンスの初期化がより明示的かつ一貫した方法で行われるため、一般的には推奨されます。ただし、特定のケースではconstructorを使用せずにクラスを定義することも可能です。

classの登場

classは、ECMAScript 2015(またはES6)で導入されました。ES6は、JavaScriptの最新の標準規格であり、2015年にリリースされました。

ES6の導入により、classキーワードを使用してクラスベースのオブジェクト指向プログラミングがサポートされるようになりました。これにより、クラス、コンストラクタ、メソッド、継承などのオブジェクト指向の概念をより直感的に利用できるようになりました。

それ以前のJavaScriptでは、プロトタイプベースのオブジェクト指向プログラミングが主流であり、クラスの概念はありませんでした。しかし、ES6以前でもオブジェクト指向のパターンを実現するために、関数やプロトタイプを使用する方法がありました。

ES6の導入により、class構文が追加されたことで、より直感的で明瞭なクラスベースのオブジェクト指向プログラミングが可能となりました。この機能の導入により、JavaScriptの開発者はより効果的にオブジェクト指向のコードを書くことができるようになりました。

classをfunctionに置き換える方法

上記で紹介したように古いやり方で同じことができます。おまけとして理解しておくくらいでOKです。

classを使用せずにfunctionで同じコードを書き換えると以下のようになります:

function Person(name, age) {
  this.name = name;
  this.age = age;
  console.log("Personインスタンスが作成されました");
}

const person1 = new Person("Alice", 25);
console.log(person1.name); // 出力: "Alice"
console.log(person1.age); // 出力: 25


上記のコードでは、classの代わりにfunctionを使用してPerson関数を定義しました。functionの場合、コンストラクタとして使用する関数の名前は大文字で始める慣例があります(パスカルケース)。

Person関数内部では、thisキーワードを使用してインスタンス変数を初期化します。thisは、新しく作成されるインスタンスを指します。console.log()文は、インスタンスが作成されたことを示すために表示されます。

インスタンスの作成にはnewキーワードを使用し、Person関数に適切な引数を渡します。インスタンス変数には、person1.nameperson1.ageのようにアクセスできます。

classを使ったモジュールの例

以下は、classを使用して簡単なモジュールを作成する例です。このモジュールは、数値の加算と乗算を行う機能を提供します。

class Calculator {
  add(a, b) {
    return a + b;
  }

  multiply(a, b) {
    return a * b;
  }
}

export default Calculator;


上記のコードでは、Calculatorというクラスが定義されています。このクラスは、add()multiply()という2つのメソッドを持っています。add()メソッドは2つの数値を加算し、multiply()メソッドは2つの数値を乗算します。

export default Calculator;の部分は、このモジュールを他のファイルで使用するためにエクスポートしています。このモジュールを他のファイルでインポートすることで、Calculatorクラスの機能にアクセスすることができます。

別のファイルでこのモジュールを使用する例を示します:

import Calculator from './Calculator';

const calc = new Calculator();

console.log(calc.add(2, 3)); // 出力: 5
console.log(calc.multiply(2, 3)); // 出力: 6


上記のコードでは、Calculatorモジュールをimport文を使用してインポートしています。インポートしたCalculatorクラスのインスタンスを作成し、add()multiply()メソッドを使用して計算を行っています。

このように、classを使用してモジュールを作成すると、関連する機能をグループ化し、再利用可能なコードを簡単に作成することができます。

クラスインスタンス変数

クラス内で変数を宣言する際に、letconstなどの修飾子を使用しない場合、変数はデフォルトでインスタンス変数として扱われます。インスタンス変数はクラスのインスタンスごとに異なる値を持つことができるプロパティです。

クラスのインスタンス変数と通常の変数の違いを以下の例を使用して説明します。

class Circle {
  radius; // クラスのインスタンス変数

  constructor(radius) {
    this.radius = radius;
  }

  getArea() {
    return Math.PI * this.radius * this.radius;
  }
}

const circle1 = new Circle(5); // クラスのインスタンスを作成
console.log(circle1.radius); // 出力: 5
console.log(circle1.getArea()); // 出力: 78.53981633974483

const radius = 10; // 通常の変数
const area = Math.PI * radius * radius;
console.log(area); // 出力: 314.1592653589793


この例では、Circleクラスと通常の変数を使用して円の面積を計算しています。

Circleクラスのインスタンス変数radiusは、クラス内で宣言されています。これは各インスタンスごとに異なる値を持つことができます。circle1インスタンスを作成し、そのradiusプロパティに値5を設定しています。getAreaメソッドを使用して、radiusプロパティを参照して円の面積を計算しています。

一方、通常の変数radiusは、クラスの外で宣言されています。これは単なる数値の変数であり、特定のオブジェクトやクラスに関連付けられていません。ここでは、radius変数を使用して直接円の面積を計算しています。

クラスのインスタンス変数は、クラス内のメソッドからthisキーワードを介してアクセスできます。それぞれのインスタンスは異なる値を持つことができます。

通常の変数は、そのスコープ内でのみ有効であり、クラス内のメソッドから直接アクセスすることはできません。通常の変数は、特定のスコープ内でのみ使用されるデータを表します。

要約すると、クラスのインスタンス変数は、クラスのインスタンスごとに異なる値を持ち、クラス内のメソッドからアクセスできます。一方、通常の変数は、そのスコープ内でのみ有効であり、クラスとは直接関係がありません。

クラスインスタンス変数の代わりにletを使えるか

クラスのインスタンス変数の代わりに let を使用することもできます。ただし、その場合は以下の点に注意する必要があります。

  1. スコープの違い: let で宣言された変数は、ブロックスコープを持ちます。これは、変数が宣言されたブロック内でのみ有効であることを意味します。一方、クラスのインスタンス変数は、クラス内のどのメソッドからでもアクセスできます。
  2. インスタンスごとの値の管理: let で宣言された変数は、通常の変数と同様に、そのスコープ内でのみ有効です。したがって、クラスのインスタンスごとに異なる値を保持する場合、各インスタンスに対して個別の変数を定義する必要があります。

以下の例を使用して、let を使用してクラスのインスタンス変数の代わりに変数を宣言する方法を示します。

class Circle {
  constructor(radius) {
    let localRadius = radius;

    this.getArea = function() {
      return Math.PI * localRadius * localRadius;
    };
  }
}

const circle1 = new Circle(5);
console.log(circle1.getArea()); // 出力: 78.53981633974483

const circle2 = new Circle(10);
console.log(circle2.getArea()); // 出力: 314.1592653589793


上記の例では、Circle クラスのコンストラクタ内で let を使用して localRadius 変数を宣言しています。localRadius はクロージャとして getArea メソッド内で利用されており、各インスタンスごとに異なる値を保持します。

ただし、この方法ではインスタンス変数と同じくらい簡潔でスマートなコードを実現することはできません。クラスのインスタンス変数を使用することで、インスタンスごとのデータの管理やクラスのメソッドからのアクセスがより直感的になります。したがって、通常はクラスのインスタンス変数を使用することが推奨されます。

まとめ

クラスを学ぶことは、オブジェクト指向プログラミングの基礎を理解し、柔軟で効果的なコードを作成するための重要なスキルを身につけることになります。クラスはコードの構造化や再利用性の向上、保守性の向上など、多くのメリットを提供します。是非、がんばりましょう。

お疲れ様でした。