【JS学習その⑧】コンストラクター関数 ～prototype・new演算子・instanceof～

#JS学習シリーズの目的
このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)

#コンストラクター関数とは
「**新しくオブジェクトを作成するための雛形となる関数**」

コンストラクター関数は、一般的な関数とは異なり、専用の`new`演算子を使ってオブジェクトを生成します。

```main.js
function A() {
    this.prop = 0;
}

const obj = new A();
```
上記のコードのように、
`new`演算子で作成したオブジェクトを**インスタンス**と言い、`new`演算子でインスタンスを作成することを**インスタンス化**と言います。
※コンストラクター関数では、慣例として関数名の**先頭を大文字**で書きます。

#prototypeとは
「**オブジェクトに存在する特別なプロパティー**」
「**コンストラクター関数と合わせて使用**」

```main.js
function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.hello = function() {
    console.log('hello ' + this.name);
}

const bob = new Person('Bob', 18);
const tom = new Person('Tom', 33);
const sun = new Person('Sun', 20);

bob.hello(); /*hello Bob*/
tom.hello(); /*hello Tom*/

console.log(bob);
```

上記のコードでは、
`prototype`プロパティに`hello`メソッドを追加して、それを`new`演算子でインスタンス化したオブジェクトから呼び出しています。
よって、実行結果はそれぞれコメントした内容となります。

ここで、`console.log(bob)`の結果を見てみると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713434/0cbfd1ac-3e97-366e-9e23-fd0a38b47cb7.png)
上記の画像のように、インスタンスの中に存在する`__proto__`の中に、先ほど`prototype`に追加した
`hello`メソッドが格納されています。

このことから、**インスタンス化した際には`prototype`の参照が`__proto__`にコピーされる**ということが分かります。

↑は、非常に重要なので理解しておきましょう！

##なぜprototypeを使うのか
前述したように、コンストラクター関数でメソッドを定義する際は、`prototype`を使って定義します。
ただ、`prototype`を使わなくても直接コンストラクター関数に定義することはできます。
では、なぜ`prototype`を使って定義するのでしょうか？
それは、「**メモリの効率化**」のためです。
下記のコードを見てみましょう。

```main.js
function Person1(name, age) {
    this.name = name;
    this.age = age;
    this.hello = function() {
        console.log('hello ' + this.name);
    }
}

const bob = new Person1('Bob', 18);
bob.hello(); /*hello Bob*/

function Person2(name, age) {
    this.name = name;
    this.age = age;
}

Person2.prototype.hello = function() {
    console.log('hello ' + this.name);
}

const tom = new Person2('Tom', 33);
tom.hello(); /*hello Tom*/
```

上記のコードでは、
`Person1`ではコンストラクター関数に直接メソッドを定義して、`Person2`では`prototype`にメソッドを定義しています。
どちらも、インスタンス化してメソッドを呼び出した際の実行結果は同じです。
しかし、この2つの違いは、

* Person1のインスタンスでは、メソッドそのものをメモリ空間にコピーして作成している
* Person2のインスタンスでは、`prototype`に定義したメソッドの参照を`__proto__`にコピーしている

つまり、`Person1`の書き方だと、インスタンスを生成する度に`hello`メソッドを追加する必要があるので、余分なメモリを消費することになります。
一方、'Person2'の書き方だと、インスタンスを生成した際に`prototype`の参照をコピーするため、オブジェクトに格納されているメソッドの参照先は全て一致するので、余分なメモリを消費せずに効率的にプログラムを動かすことができます。

そのため、この`prototype`というオブジェクトは、JavaScriptの仕組みを支える重要な技術となります。

#new演算子
まず、`new`演算子とは
「**コンストラクター関数からインスタンスを作成するために使用する演算子**」

これまでも`new`演算子は使ってきましたが、`new`演算子でコンストラクター関数からインスタンスを作成する際、
コンストラクター関数の戻り値によって動きが変わってきます。

* コンストラクター関数の戻り値が**オブジェクト**の場合は、**コンストラクターが返す`return`のオブジェクトを新しいインスタンスオブジェクトとして呼び出し元に返す**
* コンストラクター関数の戻り値が**オブジェクト以外、もしくは戻り値の`return`が定義されていない**場合は、**コンストラクターの`prototype`の参照をインスタンスの`__proto__`にコピーして、コンストラクターで使用している'this'を呼び出し元に返却する**(※インスタンスを'this'の参照先としてコンストラクター関数を実行)

```main.js
function F1(a, b) {
    this.a = a;
    this.b = b;
    return {};
}

const instance1 = new F1(1, 2);
console.log(instance1); /*{}*/

function F2(a, b) {
    this.a = a;
    this.b = b;
    return 1;
}

const instance2 = new F2(1, 2);
console.log(instance2); /*{a: 1, b: 2}*/
```
上記のコードでは、
`F1`では、戻り値が**オブジェクト**なので、`new`演算子でインスタンス化した時、`return`のオブジェクトをインスタンスオブジェクトとして返すので、結果は(今回の場合)`{}`(空のオブジェクト)となります。

`F2`では、戻り値が**オブジェクト以外のプリミティブ値**なので、'new'演算子でインスタンス化した時、(今回は`prototype`でメソッドを定義していませんが)'prototype'の参照を`__proto__`にコピーして、'this'の参照するオブジェクトはインスタンス(今回の場合は`instance2`)になります。

#instanceofとは
「**どのコンストラクターから生成されたオブジェクトかを確認する**」

```main.js
function F(a, b) {
    this.a = a;
    this.b = b;
}

F.prototype.c = function() {}

const instance = new F(1,2);

console.log(instance instanceof F); /*true*/
console.log(instance.__proto__ === F.prototype) /*true*/
```
上記のコードでは、
`console.log(instance instanceof Object)`で、`instance`が`F`のインスタンスなので、`true`が返ってきます。
ちなみに、`console.log(instance.__proto__ === F.prototype)`で、`instance.__proto__`と`F.prototype`が同じ参照先のオブジェクトなのでこれも`true`が返ってきます。

##instanceofの使用例
`instanceof`は**プロトタイプチェーン**をさかのぼって検証を行うので、例えば次のように関数に渡す引数が**配列**か**オブジェクト**かで条件分岐して関数を実行することもできます
(※**プロトタイプチェーン**・・・プロトタイプの**多重形成**を**プロトタイプチェーン**と言う)

```main.js
function fn(arg) {
    if(arg instanceof Array) {
        arg.push('value');
    } else if (arg instanceof Object) {
        arg['key'] = 'value';
    }
    console.log(arg)
}

fn([]) /*["value"]*/
```

#まとめ
いかがでしたでしょうか。
コンストラクター関数は、ES6から導入されたクラス構文の基礎となる部分なので、しっかり理解しておきましょう！