JavaScriptにおけるオブジェクト指向プログラミング

JavaScriptにおけるオブジェクト指向プログラミング

JavaScriptのオブジェクト指向プログラミングは、プロトタイプベースの継承を利用しています。これは、オブジェクトが他のオブジェクト（プロトタイプ）から機能を継承する仕組みです。

## 目次：
- [プロトタイプ](#プロトタイプ)
- [ファクトリ関数](#ファクトリ関数)
- [コンストラクタ関数](#コンストラクタ関数)
- [クラス構文](#クラス構文)
- [JavaScriptにおける継承: extendsとsuper](#javascriptにおける継承-extendsとsuper)

## プロトタイプ
プロトタイプとは？

オブジェクトのプロパティやメソッドを共有するためのテンプレートオブジェクトです。
各オブジェクトは一つのプロトタイプを持ち、そのプロトタイプからメソッドやプロパティを継承します。

使い方の例

配列オブジェクトの push や pop メソッドは、各配列が持っているわけではなく、プロトタイプオブジェクトから継承されています。
Array.prototype.push などがその例です。

## ファクトリ関数
定義と例

オブジェクトを作成する関数。

例: makeColor 関数

```js
function makeColor(r, g, b) {
    const color = {};
    color.r = r;
    color.g = g;
    color.b = b;
    color.rgb = function() {
        return `rgb(${this.r},${this.g},${this.b})`;
    };
    return color;
}
const firstColor = makeColor(35, 255, 150);
```

欠点

各オブジェクトに同じメソッドを個別に作成してしまうため、メモリの無駄が生じる。

## コンストラクタ関数

定義と例

new キーワードと共に使用される関数。新しいオブジェクトを作成し、そのプロパティとメソッドを初期化します。

例: Color コンストラクタ関数

```js
function Color(r, g, b) {
    this.r = r;
    this.g = g;
    this.b = b;
}
const newColor = new Color(255, 0, 0);
```

メリット

プロトタイプを利用することで、共通のメソッドを一箇所にまとめることができる。
プロトタイプメソッドの定義


```js
Color.prototype.rgb = function() {
    return `rgb(${this.r},${this.g},${this.b})`;
};
```
:::note info
MDN参照：

1. 空のプレーンな JavaScript オブジェクトを生成します。
2. 新しいオブジェクトにプロパティ (__proto__) を追加し、コンストラクター関数のプロトタイプオブジェクトに結びつけます。

メモ: コンストラクター関数のプロトタイプに追加されたプロトタイプやオブジェクトは、そのためそのコンストラクター関数で（new を使用して）生成されたすべてのインスタンスからアクセスできます。

3.新しく生成されたオブジェクトインスタンスを this コンテキストとして結びつけます。 （すなわち、コンストラクター関数内の this へのすべての参照は、最初のステップで作成されたオブジェクトを参照するようになります。）
4.関数がオブジェクトを返さない場合は this を返します。
:::

## クラス構文

定義と例

ES6から導入された糖衣構文（シンタックスシュガー）で、コンストラクタ関数とプロトタイプの処理をよりシンプルに書けるようにしたもの。

例: Color クラス
```js
class Color {
    constructor(r, g, b, name) {
        this.r = r;
        this.g = g;
        this.b = b;
        this.name = name;
        this.calcHSL();
    }

    innerRGB() {
        return `${this.r}, ${this.g}, ${this.b}`;
    }

    rgb() {
        return `rgb(${this.innerRGB()})`;
    }

    rgba(a = 1.0) {
        return `rgba(${this.innerRGB()},${a})`;
    }

    hex() {
        return '#' + ((1 << 24) + (this.r << 16) + (this.g << 8) + this.b).toString(16).slice(1);
    }

    fullySaturated() {
        return `hsl(${this.h}, 100%, ${this.l}%)`;
    }

    hsl() {
        return `hsl(${this.h}, ${this.s}%, ${this.l}%)`;
    }

    opposite() {
        const newHue = (this.h + 180) % 360;
        return `hsl(${newHue}, ${this.s}%, ${this.l}%)`;
    }

    calcHSL() {
        let { r, g, b } = this;
        r /= 255;
        g /= 255;
        b /= 255;

        let cmin = Math.min(r, g, b),
            cmax = Math.max(r, g, b),
            delta = cmax - cmin,
            h = 0,
            s = 0,
            l = 0;

        if (delta == 0) h = 0;
        else if (cmax == r)
            h = ((g - b) / delta) % 6;
        else if (cmax == g)
            h = (b - r) / delta + 2;
        else
            h = (r - g) / delta + 4;

        h = Math.round(h * 60);
        if (h < 0) h += 360;
        l = (cmax + cmin) / 2;
        s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));

        s = +(s * 100).toFixed(1);
        l = +(l * 100).toFixed(1);

        this.h = h;
        this.s = s;
        this.l = l;
    }
}

const red = new Color(255, 67, 89, 'tomato');
const white = new Color(255, 255, 255, 'white');
```

メリット

コンストラクタ関数とプロトタイプメソッドを一箇所にまとめて定義でき、コードが読みやすくなる。

:::note info
まとめ
1.プロトタイプ
 オブジェクトが共有するプロパティやメソッドのテンプレート。

2.ファクトリ関数
簡単にオブジェクトを作成できるが、メソッドの重複が生じる。

3.コンストラクタ関数
new キーワードを使って新しいオブジェクトを作成。
プロトタイプを利用してメソッドの重複を避ける。

4.クラス構文
コンストラクタ関数とプロトタイプの糖衣構文。
コードがシンプルで読みやすくなる。
:::

上記の Color クラスでは、RGB値からHSL値を計算する calcHSL メソッドや、RGB表記、RGBA表記、HEX表記を返すメソッド、HSL表記を返すメソッド、補色を計算するメソッドなどが定義されています。これにより、色の操作が簡単に行えるようになっています。

red と white のインスタンスは、それぞれ Color クラスを使って作成され、色のプロパティやメソッドを使用することができます。

```js
const red = new Color(255, 67, 89, 'tomato');
const white = new Color(255, 255, 255, 'white');

console.log(red.rgb()); // rgb(255, 67, 89)
console.log(white.hex()); // #ffffff
console.log(red.opposite()); // hsl(180, 100%, 50%)
```

## JavaScriptにおける継承: extendsとsuper

JavaScriptのクラス継承は、共通の機能を一つの親クラスに集約し、子クラスでその機能を再利用することで、コードの重複を減らし、効率的に開発する手法です。
この継承はextendsキーワードとsuperキーワードを使って実現されます。

```js
class Cat {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    eat() {
        return `${this.name}がご飯を食べている。`;
    }
    meow(){
        return 'にゃー';
    }
}

class Dog {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    eat() {
        return `${this.name}がご飯を食べている。`;
    }
    bark() {
        return 'わんわん!!';
    }
}
```

このコードでは、nameやageの初期化、eatメソッドが重複しています。

#### 継承を使った改善
共通のプロパティとメソッドを親クラスとしてまとめ、子クラスでその親クラスを継承します。

```js
class Pet {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    eat() {
        return `${this.name}がご飯を食べている。`;
    }
}

class Cat extends Pet {
    meow(){
        return 'にゃー';
    }
}

class Dog extends Pet {
    bark() {
        return 'わんわん!!';
    }
}
```

このコードでは、CatとDogが共通のプロパティやメソッドをPetクラスから継承しています。

#### extendsキーワード

extendsキーワードを使って、CatとDogがPetクラスを継承しています。これにより、Petクラスのプロパティやメソッドを自動的に利用できます。

```js
class Cat extends Pet {
    meow(){
        return 'にゃー';
    }
}

class Dog extends Pet {
    bark() {
        return 'わんわん!!';
    }
}
```

#### superキーワード

superキーワードを使うことで、親クラスのコンストラクタを呼び出すことができます。これにより、子クラスのコンストラクタで親クラスのプロパティを初期化できます。

```js
class Cat extends Pet {
    constructor(name, age, color){
        super(name, age);
        this.color = color;
    }
    meow(){
        return 'にゃー';
    }
}
```

super(name, age)を使って、Petクラスのコンストラクタを呼び出し、nameとageを初期化しています。その後、Catクラス独自のプロパティであるcolorを追加しています。

完全な例
以下は、Petクラスを継承したCatとDogの完全な例です。

```js
class Pet {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    eat() {
        return `${this.name}がご飯を食べている。`;
    }
}

class Cat extends Pet {
    constructor(name, age, color){
        super(name, age);
        this.color = color;
    }
    meow(){
        return 'にゃー';
    }
}

class Dog extends Pet {
    bark() {
        return 'わんわん!!';
    }
}

const myCat = new Cat('ミケ', 3, '三毛');
console.log(myCat.eat()); // ミケがご飯を食べている。
console.log(myCat.meow()); // にゃー
console.log(myCat.color); // 三毛

const myDog = new Dog('ポチ', 5);
console.log(myDog.eat()); // ポチがご飯を食べている。
console.log(myDog.bark()); // わんわん!!
```

::: note info

extends: クラスを継承するためのキーワード。
子クラスが親クラスのプロパティやメソッドを利用できるようになります。

super: 親クラスのコンストラクタやメソッドを呼び出すためのキーワード。
子クラスのコンストラクタで親クラスのプロパティを初期化する際に使います。

継承を使うことで、コードの重複を避け、再利用性を高めることができます。親クラスに共通の機能を持たせ、子クラスで独自の機能を追加することで、効率的な開発が可能となります。
:::