他言語学習者向け：JavaScript/ECMAScriptプロトタイプ及びクラス入門

NOTE: この記事は、弊社内のオンボーディング資料を公開したものです。

* [他言語学習者向け：JavaScript/ECMAScript文法速習リファレンス](https://qiita.com/awa2/items/78c3619b6c418bcc107b)
* [他言語学習者向け：JavaScript/ECMAScriptプロトタイプ及びクラス入門](https://qiita.com/awa2/items/7858b72c683622323cdc)
* [NodeJS入門: イベントループとlibuv編](https://qiita.com/awa2/items/33920620041bad92f698)

主に他言語の経験がある人向けのJS/ECMAScriptのプロトタイプに関しての理解を深めるための資料です。

JSに初めて触れる方はこちらを先に読んでください。

https://qiita.com/awa2/items/78c3619b6c418bcc107b

## JSのグローバル空間
JavaScriptのグローバル空間は、巨大の1つのオブジェクトとようなものとして表現されており、コード内で宣言される変数は、このグローバル空間のメンバ変数（プロパティ、メソッド）の一部とみなされます。

```JavaScript
var a = 'my variable!' // constやletではブロック内宣言となるので、ここではvarを使っています
console.log(globalThis.a); // my variable!
```
このグローバルスコープ自身を表す特殊な変数として、`globalThis` が用意されています（ES6以降）。

`Object`のようなデータ型や`Function`、`Array`のような組み込みオブジェクト、`window`のようなスコープもグローバル空間に所属しており、私たちが利用できるようになっています。

## プロトタイプベースのJavaScript
プロトタイプベースとは、オブジェクトの生成にあたり、他のオブジェクトの一部を参照して新たなオブジェクトを作成するオブジェクト指向プログラミングの一種です。
ここでいう、「他のオブジェクトの一部」をプロトタイプと呼びます。

現在では、すでにECMAScriptやTypeScriptではクラスの概念が導入されており、JavaやC#のようなクラスベースでのオブジェクト指向プログラミングが可能になっていますが、JavaScriptは元来、プロトタイプベースであり、一部の特殊な振る舞いを理解するには、この仕組みを理解することが欠かせません。

### 超柔軟なプロトタイプベース
クラスベースの言語では、型であるクラスと値であるインスタンスは厳密に分離していますが、プロトタイプベースであるJavaScriptにおいては、どちらもオブジェクトとして取り扱われます。

これにより、クラスをコードで動的に拡張したり、変更することすら可能です。
この特性は、JavaScriptの際立った柔軟性の1つともいえます。

### プロトタイプとクラスの違い
では、一例として、 `Array`型のデータを作成する際に、プロトタイプがどのように働くのか、その背景を考えてみましょう。
Arrayは配列を表す組み込みオブジェクトです。次のように利用できます。
```JavaScript
const ary = new Array('a','b'); // ary => ['a', 'b']
```
これは、その他の言語でいえば、`Array`クラスから `ary` インスタンスを作成したとも言い換えることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247444/a28347ad-d288-215c-1ec8-263369353d31.png)


`ary`には、Arrayが持っていた関数が、プロパティ（メソッド）として利用できるようになっています。
```JavaScript
ary.forEach(element => console.log(element));
```
例えば、上記の `forEach` は要素を列挙するメソッドですね。
では、この `forEach`の処理が実際に書かれた関数はどこに実装されているのでしょうか？
クラスベースの言語であれば、次のようなものと考えるでしょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247444/cdc376c4-909e-80fc-ff99-ff6e39a40040.png)

```JavaScript
class Array {
  forEach(callback){
    /** ここに処理が定義されているはず？ **/
  }
}
```
しかし、昔のJavaScriptにはクラスという概念は存在しないはずです。
この`new`キーワードはどのように処理されていたのでしょうか？

### プロトタイプの実態
JavaScriptでは長らくクラスという概念が存在せず、クラスからインスタンスを生成するような考え方とは別の方法が用いられてきました。それがプロトタイプベースです。

プロトタイプベースであるJavaScriptでは、`Array.prototype` に`forEach`が登録されています。
確認してみましょう。
```JavaScript
console.log(Array.prototype.forEach); // [Function: forEach]
```

つまり、Arrayの固有のメソッドは `Array.prototype` に格納されており、 `new` つまりコンストラクタが動作するタイミングで、aryにも同様のメソッドがセットされたことがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247444/99dd54e3-407a-f68d-b0b5-0705b3bd91a5.png)


では、ここで `forEach`メソッドを試しに削除してみましょう。
オブジェクトから特定のプロパティやメソッドを削除するには `delete` を使います。
```JavaScript
delete ary.forEach
console.log(ary.forEach) // あれれ？消せない？？？
```
deleteを実行したにも関わらず、forEachが存在しています。これはなぜでしょうか。

### インスタンスに存在しないプロパティを格納する内部プロパティ
実は、`forEach`は、`ary`に直接登録されているのではなく、その内部プロパティ `[[Prototype]]` に含まれています。

この内部プロパティの表現方法はJavaScriptの処理系によって異なりますが、ここでは、一般的な `__proto__`で見てみましょう。

```JavaScript
console.log(ary.__proto__.forEach); // ここに入っていた！
```
**実は、元となったArrayオブジェクトから引き継がれたメソッドは、全て `__proto__` に格納されています。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247444/b5df462e-6b07-ce11-cbad-0773e02a63d7.png)


便宜的に、クラスとインスタンスという用語で説明すると、

* Arrayクラスのメソッドは `Array.prototype` に定義されている
* `new Array()`で作成されたインスタンスは、内部に `__proto__`プロパティがあり、そこにメソッドを格納する
* **インスタンスに該当するメソッドやプロパティがなければ、 `__proto__`プロパティを参照し、そこから呼び出す**

という仕組みになっていることがわかります。

### なぜ内部プロトタイプを使うのか
`ary.forEach`が、実際には`ary.__proto__` に登録されていることはわかりました。
しかし、なぜ直接ではなく、`ary.__proto__`に登録しているのでしょうか。

実は、`ary.__proto__`は、`Array.prototype`へのゲッター及びセッターとしての機能を持っています。
これにより、Arrayから`new` によって作成された全ての変数が、同じメソッドを参照している形となります。

もし直接、`ary.forEach` にしてしまうと、どのメンバ変数が元のArrayから継承されたメソッドなのかわからなくなってしまいます。

関数が第一級関数であるJavaScriptの世界では、確かに任意の関数を任意の変数に自在に登録することができますが、「何が固有のプロパティで、何が引き継いだプロパティなのか」を明確にするためにプロトタイプが利用されている、と考えてください。

より詳しく知りたい人は、ぜひMDNの記事を読んでください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain


### クラス登場以前のJavaScriptにおけるクラス表現
ここまでで、prototypeと__proto__の関係について、何となく理解できたかと思います。

さて、先述の通り、JavaScriptにはもともとクラスという概念がありませんでした。
ただ、prototypeを駆使することで、クラスのようにメソッドを定義できたため、クラスベースのオブジェクト指向プログラミングのように`new`を用いてインスタンスを生成する方法も使うことができました。

では、実際にクラスが導入される前のJavaScriptでは、どのようにクラスを表現していたのでしょうか？

こんな形で定義されていました。
```JavaScript
// コンストラクタは関数定義を用いる
var MyClass = function(arg) {
  this.arg = arg;
}

// メソッドやプロパティはprototypeに登録する。
MyClass.prototype.getArg = function() {
  return this.arg;
}
MyClass.prototype.sayHello = function() {
  console.log('hello! ' + this.arg);
}

var myInstance = new MyClass('test');
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247444/6a11277f-0068-ec53-bbf6-ac65aeb8932b.png)

ES6以降、クラスが導入されたため、この表現は過去のものとなりましが、現在でも古いNPMライブラリや古いブラウザ向けのトランスパイルでこの方式のクラス定義を見ることができます。

## クラス
ECMAScriptでは、ES6よりクラス宣言とクラス式が導入され、一般的なカプセル化を実現するためのクラス表現が可能になりました。

次のように記述します。
```JavaScript
class Rectangle {
  #_height = 0;
  #_width = 0;
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  
  // メソッド
  calcArea() {
    return this.height * this.width;
  }

  // アクセサ
  get height(){ return this._height; }
  set height(num){
    this._height = num >= 0 ? num : 0;
  }
  get width(){ return this._width; }
  set width(num){
    this._width = num >= 0 ? num : 0;
  }

  // 静的メソッド
  static create(num){
    return new Rectangle(num, num)
  }
}
```
ここでは、メンバ変数（フィールド）として `height` `width` をもち、`area` ゲッター、`calcArea`メソッドを持つクラスが定義されています。
`this` キーワードによってメンバ変数にアクセスできるようになっているのが分かります。


### その他の言語のクラスとの違い

#### アクセス修飾子
フィールド宣言では `#` を付与することでプライベート化することができます。
```JavaScript
class Rectangle {
  #_height = 0;
  #_width;
  constructor(height, width) {
    this.#_height = height;
    this.#_width = width;
  }
}
```
プライベートフィールドは事前宣言でないと作成できないことに注意してください。

いわゆる `protected`なアクセス修飾子は定義することができません。

#### インターフェイス・抽象クラス
型を持たないJavaScriptでは、インターフェイスの定義ができません。
同様に抽象クラス（abstract class）も利用できません。ミックスインなどの他の方法を使う必要があります。

これらの問題は、JavaScriptの型付き拡張であるTypeScriptで解決されています。

#### デストラクタ
デストラクタやファイナライザが存在しません。JavaScriptの処理系はほとんどの場合、自動的にガベージコレクションが行われるため、インスタンスのライフサイクルは不透明です。終了処理が必要な場合には、手動で終了処理を実装し、呼び出す必要があります。

#### 継承
その他の言語と同様、`extends` キーワードを用いることで、クラスを継承することができます。
```JavaScript
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // 継承元クラスのコンストラクターを呼び出し、name パラメータを渡す
  }

  speak() {
    console.log(`${this.name} barks.`);
  }
}

let d = new Dog("Mitzie");
d.speak(); // Mitzie barks.
```
継承先のクラスのコンストラクタでは、必ず `super` をコールする必要があります。
また、`super`は、継承元クラスのメソッドへのアクセスにも用いることができます。

```JavaScript
class Cat {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Lion extends Cat {
  speak() {
    super.speak(); // 継承元クラスのメソッドを呼び出す
    console.log(`${this.name} roars.`);
  }
}

let l = new Lion("Fuzzy");
l.speak();
```

### 多重継承・ミックスイン・トレイト
多重継承はできません。常に1つのクラスをスーパークラスとして継承元にすることができます。

一方、ミックスインに関しては、技巧的な方法で実現できます。
```JavaScript
let calculatorMixin = (Base) =>
  class extends Base {
    calc() {}
  };

let randomizerMixin = (Base) =>
  class extends Base {
    randomize() {}
  };

// 実際に使用する例
class Foo {}
class Bar extends calculatorMixin(randomizerMixin(Foo)) {}
```

トレイトも存在しないため、ミックスインを併用することになります。

### 静的メンバ（クラス変数）
`static` キーワードを用いることで定義されます。
```JavaScript
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  static displayName = "Point";
  static distance(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;

    return Math.hypot(dx, dy);
  }
}
```

静的メンバの内部における`this`は、クラスそのものを指すことを忘れないでください。

## TypeScriptによる拡張
TypeScriptのクラスでは、さらにアクセス修飾子、抽象クラス、インターフェイス、ジェネリクスなどを導入することができます。
また、クラスを型として取り扱えるため、さらに安全性が向上します。

詳しくは、TypeScript入門の記事で別に紹介します。