【徹底解説】JavaScriptのclassを理解する



## **classとは**

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

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

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


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


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

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


```js
const myObject = new MyClass();
```


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

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


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

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

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

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

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


```js
const myObject = new MyClass();
class MyClass {
constructor() {
this.name = "John";
}
sayHello() {
console.log(`Hello, ${this.name}!`);
}
}
```


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

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


## **クラス式**

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

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


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


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

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


```js
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()`メソッドの定義:


```js
class MyClass {
constructor(parameter1, parameter2) {
// 初期化処理を記述
}
}

```



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

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


```js
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つの引数を受け取り、`name`と`age`というインスタンス変数を初期化します。`console.log()`文は、`constructor()`メソッドが呼び出されたことを示すために表示されます。


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

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

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

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


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


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

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

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


## **constractorを使用しない方法**

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


```js
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`で同じコードを書き換えると以下のようになります：


```js
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.name`や`person1.age`のようにアクセスできます。


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

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


```js
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`クラスの機能にアクセスすることができます。

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


```js
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`を使用してモジュールを作成すると、関連する機能をグループ化し、再利用可能なコードを簡単に作成することができます。


## **クラスインスタンス変数**

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

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


```js
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` を使用してクラスのインスタンス変数の代わりに変数を宣言する方法を示します。


```js
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` メソッド内で利用されており、各インスタンスごとに異なる値を保持します。

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


## **まとめ**

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

お疲れ様でした。