JavaScriptのオブジェクト指向が10%理解できる記事（実践編）

最近、[1人が読めば50人がオブジェクト指向を理解できそうな素晴らしい記事](https://qiita.com/gorillab/items/b2f8e39d7cc23ad505f9)が書かれたのは皆さんの記憶に新しいことと思います。それを読んでオブジェクト指向を理解した皆さんは次はぜひオブジェクト指向を実践してみたいと思ったことでしょう。

この記事では**JavaScript**にスポットを当てて、JavaScriptにおけるオブジェクト指向に入門します。JavaScriptはWebページやWebアプリの制作において未だ必修言語の地位を占めているといっても良い言語ですが、その利用法には人や業種によって温度差があります。何なら、JavaScriptのオブジェクト指向的機能を（明示的には）使わなくてもある程度のものが作れてしまうという人もいることでしょう。そこで、そのような人たちでもJavaScriptでオブジェクト指向的なプログラムを書く基本が分かる記事を目指します。

対象読者は**JavaScriptをやっているけどオブジェクト指向的なプログラムをあまり書かない人**や**JavaScriptをやっていないけどJavaScriptでオブジェクト指向的なプログラムをどう書くのか気になる人**などです。この記事ではJavaScriptの言語機能を中心に解説しますので、オブジェクト指向とは何かみたいな情報は他の記事をご参照ください。

# クラスベースとプロトタイプベース
言語機能の話に入る前に、少しだけオブジェクト指向の分類の話をします。オブジェクト指向のプログラミング言語が**クラスベース**と**プロトタイプベース**に分類されるという説は古くから有名で、JavaScriptは後者のプロトタイプベースに属する言語の代表格となっています。

ですので、JavaScriptには本来（ES2015まで）クラスという用語はありませんでした。JavaScriptが採用していたプロトタイプベースでは、オブジェクトは他のオブジェクト（プロトタイプ）への参照を保持しており、自身で処理できないもの（メソッドなど）はプロトタイプに移譲します。これは、クラスベースの言語においてオブジェクトがクラスへの参照を持っており、自身が行うべき処理がクラスによって決められるのと対照的です。

その一方で、JavaScriptには`new`のようにクラスベース言語っぽい構文も持ち合わせており、クラスベース的な考え方でコードが書けるようにもなっています。これがJavaScriptの難しいところです。

# 原始時代のJavaScriptとオブジェクト指向（ES3）
いきなり最新鋭の記法を紹介してもよいのですが、JavaScriptにおけるオブジェクト指向をより理解してもらうために、古い書き方から見ていくことにします。例えば、Animalクラスとそれを継承したCatクラスを作ってみることにしましょう。（先程も説明したようにこの時代のJavaScriptにはクラスという概念は無いですが、便宜上クラスという呼び方をすることにします。）

```js
// Animalクラスの定義
function Animal(name) {
    this.name = name;
}
// Animalクラスのspeakメソッドを定義
Animal.prototype.speak = function() {
  console.log(this.name + "「鳴き声」");
};
// Animalクラスのsleepメソッドを定義
Animal.prototype.sleep = function() {
  console.log(this.name + "「zzz」");
};

// Catクラスの定義
function Cat(name) {
  // 親クラスのコンストラクタを実行
  Animal.call(this, name); 
}
// 親クラスを継承
Cat.prototype = new Animal();
// speakメソッドを定義
Cat.prototype.speak = function() {
  console.log(this.name + "「にゃーん」");
};

// Animalのインスタンスを作成
var myAnimal = new Animal("ポチ");
myAnimal.speak(); // "ポチ「鳴き声」"
myAnimal.sleep(); // "ポチ「zzz」"

// Catのインスタンスを作成
var myCat = new Cat("たま");
myCat.speak(); // "たま「にゃーん」"
myCat.sleep(); // "たま「zzz」"
```

この例ではspeakメソッドとsleepメソッドを持ったAnimalクラスとCatクラスを作り、それぞれのインスタンスを試しに作っています。では、順番に見ていきましょう。

## クラスの定義

```js:
// Animalクラスの定義
function Animal(name) {
    this.name = name;
}
```

これがAnimalクラスの定義ですが、この`function`構文はどうみても**関数定義**ですね。そう、再三述べている通りJavaScriptにはクラスなんてものはなく、代わりに使うのが**関数**なのです。この関数は、クラスベースでいうところの**コンストラクタ**の役割を果たすものです。（とはいえ、純粋なプロトタイプベース言語ではコンストラクタも必要ないでしょうから、コンストラクタという概念もJavaScriptをクラスベース言語に近づけている要員のひとつです。）

このようにして作ったコンストラクタ（のようなもの）は`new`演算子で呼ぶことができます。上の例のように`new Animal("ポチ")`とすると、Animal関数がコンストラクタとして呼ばれて、Animalのインスタンスが返ってきます。Animal関数では`this.name`に代入する処理を行っているので、返ってきたインスタンスは`name`プロパティを持っています。

```js
var myAnimal = new Animal("ポチ");
console.log(myAnimal.name); // "ポチ"
```

面白いのは、クラスにするつもりが無くても関数を作った時点でそれは`new`可能になるということです。言うまでもなく、こんな無意味なことは誰もやらないでしょうが。

```js
// ログを表示するだけの関数
function log(message) {
  console.log(message);
}

// logのインスタンスを作れる！！！！！！
var myInstance = new log("foobar");
```

逆に、クラスにするつもりで作った関数を普通に呼ぶこともできます。この場合も、`this`がグローバルオブジェクトだったり`undefined`だったりするのであまり意味はありませんが。

## メソッドの定義
さて、クラス（のようなもの）なのでやはりメソッドを作りたいですね。メソッドの定義部分は以下のようになっています。

```js
// Animalクラスのspeakメソッドを定義
Animal.prototype.speak = function() {
  console.log(this.name + "「鳴き声」");
};
// Animalクラスのsleepメソッドを定義
Animal.prototype.sleep = function() {
  console.log(this.name + "「zzz」");
};
```

このコードを読むと、`Animal.prototype`に対してメソッドを生やしています。`prototype`という名前からも分かる通り、この`Animal.prototype`はAnimalのインスタンスのプロトタイプとなるオブジェクトです。

実は、関数を定義すると、自動的に付随する`prototype`オブジェクトが用意されるのです。今回の場合、`Animal`関数を定義したと同時に、`Animal.prototype`オブジェクトが空のオブジェクトとして用意されています。この`Animal.prototype`に定義したメソッドが`Animal`インスタンスのメソッドとして使用できる理由は、`new Animal("ポチ")`としたときに何が起こるかを理解すれば分かります。

`new Animal("ポチ")`は、まずその結果となる新しいオブジェクトを生成し、**そのオブジェクトのプロトタイプを`Animal.prototype`に設定します**。その後、そのオブジェクトを`this`として`Animal`関数を引数`"ポチ"`で呼び出した後、生成した新しいオブジェクトを返します[^new_func_return]。

[^new_func_return]: `Animal`関数が返り値としてオブジェクトを返した場合は違う挙動をしたりしますが、それは10%の範疇ではないので省略します。

太字になっている部分がポイントです。この操作によって、生成されるオブジェクトが`Animal`のインスタンスとして特徴づけられます。では、`Animal`インスタンスのメソッドを呼び出したときの挙動を確認しましょう。

まず前提の確認ですが、JavaScriptのオブジェクトというのはいわゆる連想配列です。オブジェクトというのは、文字列をキーとして任意の値を格納することができる構造なのです[^symbol_key]。また、それに加えて、自分のプロトタイプはどのオブジェクトかという情報を持っています。`Animal`インスタンスとして作成されたオブジェクトそれ自身は、実は`speak`メソッドや`sleep`メソッドを持っていません。このことは[hasOwnProperty](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty)を用いて確かめられます。

[^symbol_key]: 正確には、文字列だけでなくシンボルをキーにすることもできます。

```js
var myAnimal = new Animal("ポチ");

console.log(myAnimal.hasOwnProperty("speak")); // false
```

それにも関わらず、`myAnimal.speak`とすれば先ほど定義した`speak`メソッドを呼び出すことができます。これがまさに、プロトタイプのおかげなのです。

`myAnimal.speak`という参照が行われた場合、まず`myAnimal`オブジェクト自身が`speak`メソッドを持っているかどうかが調べられます。先程確かめたように、今回のオブジェクトは`speak`メソッドを持っていません。そうなると、**プロトタイプへの移譲**が行われます。`myAnimal`のprototypeは`Animal.prototype`だったので、次に`Animal.prototype`が`speak`メソッドを持っているか確かめられます。今回我々は`Animal.prototype.speak`に関数を代入したので、これは存在しています。よって、この値が`myAnimal.speak`の結果として扱われるのです。

以上が、プロトタイプによって`Animal`インスタンスが特徴づけられる仕組みです。

ところで、今の説明を鑑みるに、`myAnimal.speak`に別の関数を代入してやれば挙動を書き換えられることは明らかですね。ちょっとやってみましょう。

```js
var myAnimal = new Animal("ポチ");
console.log(myAnimal.hasOwnProperty("speak")); // false. myAnimal自身はspeakを持たない

// myAnimal.speakに代入
myAnimal.speak = function() {
  console.log(this.name + "「わんわん」");
};

console.log(myAnimal.hasOwnProperty("speak")); // true

myAnimal.speak(); // "ポチ「わんわん」"
```
このように、生成したインスタンスの挙動を後から差し替えることができます。これはプロトタイプベースなオブジェクト指向の特徴が現れていると言えるでしょう。

なお、今さらな補足ですが、`obj.foo`は`obj`自身のプロパティ（やメソッド）を最初に探してその後プロトタイプを探しますが、`obj.foo = ...`という形で代入したプロパティ（やメソッド）は常に`obj`自身のプロパティとなります。`obj`のプロトタイプに`foo`が存在したとしてもそれが上書きされるわけではなく、`obj`自身のプロパティ`foo`が新設されます[^setter_exception]。上の例では`myAnimal.speak` に代入しましたが、これは今まで存在しなかった`myAnimal`の`speak`プロパティが新設されたのであって、`Animal.prototype.speak`には触れていません。よって、他の`Animal`インスタンスには影響を与えないことになります。

[^setter_exception]: 例外はプロトタイプの`foo`がsetterを持つプロパティだった場合です。この場合は即座に`obj.foo`が新設されるのではなくプロトタイプの`foo`のsetterプロパティが呼ばれます。

## 継承
以上の説明でJavaScriptにおけるクラス（のようなもの）をどう作るのか分かりました。次の話題は**継承**です。継承はやはり（特にクラスベースの）オブジェクト指向には欠かせない概念でしょう。最初の例から`Cat`の定義を再掲します。

```js
// Catクラスの定義
function Cat(name) {
  // 親クラスのコンストラクタを実行
  Animal.call(this, name); 
}
// 親クラスを継承
Cat.prototype = new Animal();
// speakメソッドを定義
Cat.prototype.speak = function() {
  console.log(this.name + "「にゃーん」");
};
```

`Cat`クラスを作りたいので`function Cat(name){ ... }`として`Cat`関数を作るのは`Animal`の場合と同じです。今回は`Cat`に`Animal`を継承してほしいわけですが、その際に満たして欲しい要件は2つです。1つは、`Cat`のコンストラクタが呼ばれると`Animal`のコンストラクタが呼ばれること、そしてもう1つは`Animal`のメソッドが`Cat`のメソッドとしても使用可能なことです。もちろん、`Animal`のメソッドを`Cat`が上書きしたり、`Cat`がメソッドを増やしたりしても構いませんが。

まず1つ目は`Cat`コンストラクタの処理の中で実現されています。`Animal.call`というのは関数が持つメソッドで、`this`の値と引数を指定して関数を呼び出すというものです。要するにこれは、`Cat`が呼び出されたら同じ`this`と引数で`Animal`関数を呼び出すということを行っています。これにより1つ目の要件が達成されます。

2つ目は`Cat.prototype = new Animal();`の行で行われています。これをそのまま読むと`Cat.prototype`を`Animal`のインスタンスで上書きしているように読めます。（`Animal`の引数を渡していないので`name`が`undefined`になっていたりしますが、まあエラーは起きないので些細な問題です。）

これの意味を理解するには、`Cat`のインスタンスに対するメソッド呼び出しがどうなるのかを考えるのが早いでしょう。

まず、`Cat`が再定義したメソッドである`speak`については簡単です。`myCat.speak();`とすると、まず`myCat`自身は`speak`メソッドを持たないので、`myCat`のプロトタイプである`Cat.prototype`に移譲されます。`Cat.prototype.speak`は存在するのでこれが呼び出されます。

では、`sleep`を呼び出したらどうなるでしょうか。`myCat.sleep();`とした場合、`myCat`自身は`sleep`メソッドを持たないので`myCat`のプロトタイプである`Cat.prototype`に移譲されます。先ほどとは異なり、`Cat.prototype`自身は`sleep`メソッドを持っていません。JavaScriptにおいて、自分が持っていないプロパティを自身のプロトタイプに移譲するというのはどのオブジェクトにも共通する挙動です[^get_exception]。なので、次は`Cat.prototype`のプロトタイプへの移譲が発生します。

[^get_exception]: 当然ながらと言ってはなんですが、例外はあります。Proxyとかモジュール名前空間とか。しかし今回の話題には関係ないので省いています。

ここで、`Cat.prototype`は`new Animal()`によって作られた`Animal`インスタンスであったことを思い出してください。そう、`Cat.prototype`のプロトタイプは`Animal.prototype`なのです。よって次は`Animal.prototype`に移譲され、これは`sleep`プロパティを持つので、結局`myCat.sleep`は`Animal.prototype.sleep`に解決されます。

このように、**子クラスのprototypeから親クラスのprototypeへ移譲を発生させる**ことによって継承の挙動を実現しているのです。

以上で原始時代（ES3時代）におけるオブジェクト指向の説明は終わりです。以上の機能だけを武器にJavaScriptプログラマーは戦っていました。（[\_\_proto\_\_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto)とかいう裏技はありましたが。）

# 古き良き時代のJavaScriptにおけるオブジェクト指向（ES5）
さて、時代は進み、プロトタイプ周りの言語機能が強化されたES5が登場します。ES5の時代は、次のバージョンであるES2015が登場するまで続きました。

ES5時代においても先ほどのコードは大きく変化することはありませんでしたが、ES5の新機能によっていくつかの改良点が発生しました。

## `Object.create`
先ほどの実装においてまず目につく問題点がどこにあったかというと、`Cat.prototype = new Animal();`のところですね。

もし`Animal`に副作用（ログの表示とか）があった場合、`Cat`を定義するときに`Animal`が呼ばれるのは本意ではありません。つまり、ここでは`Animal`のインスタンス（`Animal.prototype`をプロトタイプに持つオブジェクト）が欲しいのであって、`Animal`コンストラクタを呼びたいわけではないのです。そこで、この問題の解決策としてES5で導入されたのが`Object.create`です。これは、**引数で与えられたオブジェクトをプロトタイプとして持つ新しいオブジェクトを作成して返す**というものです。つまり、`new`の機能のうち前半（特定のプロトタイプを持つ新しいオブジェクトを作成）のみを行い、後半（新しいオブジェクトを`this`としてコンストラクタを呼ぶ）を行わないようなものになっています。これはどちらかと言えば純粋なプロトタイプベースに回帰するような機能ですね。

これを用いることで、問題の部分はこのように書き直せます。

```js
Cat.prototype = Object.create(Animal.prototype);
```

## プロパティの属性
これは本筋とは少し離れますが、これまでのようなメソッド定義方法（`Animal.prototype.speak = ...`）は少し問題がありました。それは、そのように定義したメソッドはfor-in文や`Object.keys`で列挙されてしまうという問題です。

for-in文は、オブジェクトに存在するキー全てに対してループできる文です。

```js
var obj = {foo: 3, bar: "😇"};

for (var key in obj) {
  console.log(key + " : " + obj[key]);
}
/*
 * foo : 3
 * bar : 😇
 */
```

これは、オブジェクトに直接存在するキーだけでなく、そのプロトタイプ（やそのまたプロトタイプ）に存在するキーも列挙してくれます。

```js
var base = {hoge: "ほげ"};
var obj = Object.create(base);
obj.foo = 3;
obj.bar = "😇";

for (var key in obj) {
  console.log(key + " : " + obj[key]);
}
/*
 * foo : 3
 * bar : 😇
 * hoge : ほげ
 */
```

しかし、そうなるとおかしいですね。実は普通のオブジェクトは`Object`のインスタンスです。すなわち、`Object.prototype`をプロトタイプに持ちます。そして、`Object.prototype`にはいくつかメソッドが存在します（先程出てきたhasOwnPropertyなど）。それにも関わらずそれらは列挙されませんでした。

これは、それらのメソッドの**enumerable属性がfalseである**からです。オブジェクトのプロパティはいくつかの属性を持ち、それによりプロパティの挙動が制御されています。enumerable属性は、それがfor-in文などで列挙されるかどうかを制御する属性です。基本的に組み込みオブジェクトのメソッドはfor-in文の邪魔にならないように、enumerable属性がfalseになっています。

一方で、我々が普通に（`Animal.prototype.speak = ...`のように代入して）作成したプロパティはenumrable属性がtrueです。ということは、`Animal`のインスタンスに対してfor-in文を使うと`speak`メソッドなどが列挙されてしまうということです。実際、下の例で確かめると`name`, `speak`, `sleep`の3つが列挙されてます。

```js
var myAnimal = new Animal("ポチ");
for (var key in myAnimal) {
  console.log(key); // "name", "speak", "sleep"が表示される
}
```

`name`はまあいいですね（`Animal`コンストラクタ内で`this.name = ...`として代入しているので）。しかし、プロトタイプチェーンに存在するメソッドが出てくるのは邪魔なので望ましくありません（本当に邪魔かどうかは意見が分かれるかもしれませんが、少なくとも組み込みのオブジェクトの挙動とは違います）。

実は、ES3では我々が作るプロパティのこうした属性を制御できなかったのです。ES5ではそれを制御する方法が追加されました。例えばenumrable属性がfalseのプロパティ（やメソッド）を作るには[Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)を使います。

```js
Object.defineProperty(Animal.prototype, {
  configurable: true,
  enumrable: false,
  writable: false,
  value: function() {
    console.log(this.name + "「鳴き声」");
  },
});
```

この例ではconfigurable属性とwritable属性も明示的に指定しています。これらの意味はここではやりませんので気になる方は自分で調べてみてください。

また、先ほど出てきた`Object.create`には、新しいオブジェクトの作成と同時にこれらの属性を指定しつつ新しいプロパティを生やすことが可能な機能もついています。これを用いると、ES3時代のコードは結局以下のように書き直せます。

```js
// Animalクラスの定義
function Animal(name) {
    this.name = name;
}
Animal.prototype = Object.create(Object.prototype, {
  speak: {
    configurable: true,
    enumerable: false,
    writable: false,
    value: function() {
      console.log(this.name + "「鳴き声」");
    },
  },
  sleep: {
    configurable: true,
    enumerable: false,
    writable: false,
    value: function() {
      console.log(this.name + "「zzz」");
    },
  },
});

// Catクラスの定義
function Cat(name) {
  // 親クラスのコンストラクタを実行
  Animal.call(this, name); 
}
// 親クラスを継承してspeakメソッドを定義
Cat.prototype = Object.create(Animal.prototype, {
  speak: {
    configurable: true,
    enumerable: false,
    writable: false,
    value: function() {
      console.log(this.name + "「にゃーん」");
    },
  },
});
```

## その他の便利機能
やや余談ですが、ES5ではプロトタイプ関連の便利な機能がいくつか追加されています。

例えば、[Object.getPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf)は、あるオブジェクトのプロトタイプとなっているオブジェクトを返すメソッドです。これを使うと、`Animal`インスタンスのプロトタイプが本当に`Animal.prototype`であることを確かめることができます。

```js
var myAnimal = new Animal("ポチ");
console.log(Object.getPrototypeOf(myAnimal) === Animal.prototype); // true
```

逆に[Object.prototype.isPrototypeOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf)は、渡されたオブジェクトが自身をプロトタイプとしているかどうかを調べることができます。

```js
var myAnimal = new Animal("ポチ");
console.log(Animal.prototype.isPrototypeOf(myAnimal)); // true
```

## `instanceof`
ここで`instanceof`演算子に触れておきます。これはES3時代からある演算子で、あるオブジェクトがあるクラス（コンストラクタ）のインスタンスであるかどうか判定する演算子です。次のように使います。

```js
var myAnimal = new Animal("ポチ");

console.log(myAnimal instanceof Animal); // true. myAnimalはAnimalのインスタンスなので
console.log(myAnimal instanceof Cat); // false

var myCat = new Cat("ねこ");
console.log(myCat instanceof Animal); // true. myCatはAnimalを継承しているCatのインスタンスなので
```

この演算子も、内部的にはオブジェクトのプロトタイプがどれかという情報を使っています。先ほど紹介した`isPrototypeOf`との違いは、継承関係を考慮してくれる（ループを回すことによってプロトタイプのプロトタイプに自身があるような場合も発見してくれる）点です[^instanceof_es2015]。

[^instanceof_es2015]: あと、実は`instanceof`の挙動は`Symbol.hasInstance`で制御できるという違いもあります。

## プロトタイプの終端
先程、普通のオブジェクトは`Object.prototype`を持つと述べました。

```js
var ordinaryObj = {};

console.log(Object.getPrototypeOf(ordinaryObj) === Object.prototype); // true
```

もちろん、`Object.prototype`それ自体も、いくつかのプロパティを持っていることから分かるようにやはりオブジェクトです。では、`Object.prototype`のプロトタイプは何でしょうか。

```js
console.log(Object.getPrototypeOf(Object.prototype)); // null
```

これをやってみると、`null`になります。つまり、`Object.prototype`はプロトタイプを持たない例外的なオブジェクトなのです。

プロトタイプを持たないオブジェクトに対してプロパティを探してもなお存在しなかった場合、`undefined`が返ります。JavaScriptの挙動として有名である「オブジェクトの存在しないプロパティにアクセスするとundefinedが返る」というのは、実は「オブジェクト自身のプロパティを探す→無いので`Object.prototype`のプロパティを探す」→「それでも無いので`undefined`を返す」という段階を踏んでいることが分かりますね。

このようなプロトタイプを持たないオブジェクトは`Object.create`を使って自分で作ることもできます。そのためには`Object.create(null)`とします。

```js
var noPrototypeObj = Object.create(null);

noPrototypeObj.foo = 3;

console.log(noPrototypeObj.foo); // 3
console.log(noPrototypeObj.hasOwnProperty); // undefined
```

このオブジェクトは先ほど説明したとおり、自身が持っているプロパティ以外には全てundefinedを返します。普通のオブジェクトは`hasOwnProperty`などの`Object.prototype`が持っているプロパティ全てを間接的に持っていましたが、プロトタイプが無いオブジェクトはそのようなプロパティすら持っていません。

このようなオブジェクトは、オブジェクトを本当に連想配列として使いたい場合に適しているという説もあります。

# 現代のJavaScriptにおけるオブジェクト指向（ES2015～）

ここまで長々と説明してきましたが、今どきのJavaScriptでとりあえず動くものを書きたいならここから上の説明は要りません。残念でしたね。

ES2015からは**クラス構文**が導入されたことで、JavaScriptにおけるオブジェクト指向のやり方が様変わりしました。とりあえず、今までの例をES2015風に書き直すとこうなります。

```js

// Animalクラスの定義
class Animal {
  // コンストラクタの定義
  constructor(name) {
    this.name = name;
  }
  // speakメソッドの定義
  speak() {
    console.log(this.name + "「鳴き声」");
  }
  // sleepメソッドの定義
  sleep() {
    console.log(this.name + "「zzz」");
  }
}
// Catクラスの定義
class Cat extends Animal {
  // コンストラクタの定義
  constructor(name) {
    // 親クラスのコンストラクタを実行
    super(name);
  }
  // speakメソッドを定義
  speak() {
    console.log(this.name + "「にゃーん」");
  }
}
// もちろんAnimalやCatは今までと同様に使用可能
// Animalのインスタンスを作成
var myAnimal = new Animal("ポチ");
myAnimal.speak(); // "ポチ「鳴き声」"
myAnimal.sleep(); // "ポチ「zzz」"

// Catのインスタンスを作成
var myCat = new Cat("たま");
myCat.speak(); // "たま「にゃーん」"
myCat.sleep(); // "たま「zzz」"
```

今までの面影は全くなく、どこからどう見てもクラスベース言語ですね。prototypeとかいうよくも分からぬ奇怪な言語仕様は影も形もありません。

まずクラスを定義するには今までのように`function`宣言で関数を定義するのではなく、`class`宣言という専用の構文を用います。その中身はメソッド定義の羅列になっていて、それに混ざった`constructor`という定義でコンストラクタ（`new`時に呼び出される関数）を定義します。なお、`constructor`の定義は省略可能であり、その場合はコンストラクタは何もしない関数になります。

今までに比べて記述量が減ってすっきりした見た目ですね。

継承についても、`class`宣言時に`extends`構文を用いることで指示することができます。継承において特徴的なのは、`constructor`内にある`super`呼び出しです。コンストラクタ内で`super`を呼び出すことで、これは親クラスのコンストラクタを呼び出すものとして扱われます。従来の`Animal.call(this, name)`に比べるとだいぶ分かりやすいですね。なお、継承を行う場合、コンストラクタ内における`super`呼び出しは必須です。つまり、子クラスのコンストラクタ内で必ず親クラスのコンストラクタを呼ばないといけないということです。

## ES2015クラスの裏側
この機能はES2015で追加されたので「ES2015クラス」などと呼ばれることがあります。たいへん画期的な機能ですが、実際のところはほとんど従来のプロトタイプベースな機構の糖衣構文です。つまり、`prototype`が過去のものとして捨て去られて全く新しいクラスベースの機構が導入されたというよりは、プログラマが表面上`prototype`を意識しなくてもオブジェクト指向的なコードが書けるように改善されたということです。

せっかくこの記事の前半で長々と`prototype`に関する説明をしたところですので、ES2015クラスの裏側がどうなっているのか調べてみましょう。といっても、結局今までと同じですねということを確かめるだけですが。

まずそもそも、従来はクラスは`function Animal(name) { ... }`のように定義されていました。ということは、**`Animal`は関数オブジェクト**なのです。このことは例えば`typeof`演算子で確かめられます。

```js
console.log(typeof Animal === "function"); // true
```

ES2015の`class`構文で定義されたクラスも、やはりその実は関数オブジェクトです。ただし、`class`で作られた関数オブジェクトは**`new`を使わないと呼び出せない**という特徴があります。

というのも、`function`宣言で作られる従来方式のクラスは結局ただの関数なので、それが関数なのかクラスなのかはプログラマの意図次第です。ですから、普通の関数としても呼び出せるし、`new`を用いてクラス（コンストラクタ）としても呼び出せます。

一方、`class`で作られたクラスは、関数オブジェクトであるといえどもクラスとして用いることが意図されているのは明白です。ですから、`Animal()`のようにただの関数として呼び出すことはできません。これは、`class`が完全な糖衣構文ではない点のひとつです（他の機能ではこのような関数オブジェクトを作ることは（少なくとも現時点では）できません。）[^aside_arrow_function]

[^aside_arrow_function]: 余談ですが、これと対になる存在として、普通の関数としてしか呼ぶことができず、`new`が使えない関数オブジェクトというのもES2015で導入されました。それは[アロー関数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)です。他に[async関数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)なども`new`が使えません。

さて、`Animal`が`class`構文を用いて作られた場合であっても、その裏の挙動というのは変わっていません。ソースコードには明示的に現れませんが、裏ではちゃんと`Animal.prototype`が用意されています。

```js
// Animal.prototypeにspeakメソッドが存在するか確かめる
console.log(Animal.prototype.hasOwnProperty("speak")); // true
```

当然ながら、`Animal`のインスタンスは従来どおり`Animal.prototype`をプロトタイプに持ちます。

```js
var myAnimal = new Animal("ポチ");
console.log(Object.getPrototypeOf(myAnimal) === Animal.prototype); // true
```

継承の仕組みが従来どおりであることも確かめられます。

```js
console.log(Object.getPrototypeOf(Cat.prototype) === Animal.prototype); // true
```

ちなみに、`class`構文で作られたメソッドはちゃんとenumrable属性がfalseに設定されています。さすがですね。

## staticメソッド
`class`構文にはstaticメソッドを作成する機能もあります。`Animal`クラスにstaticな`makeChild`メソッドを作ってみましょう。

```js
class Animal {
  // コンストラクタの定義
  constructor(name) {
    this.name = name;
  }
  // speakメソッドの定義
  speak() {
    console.log(this.name + "「鳴き声」");
  }
  // sleepメソッドの定義
  sleep() {
    console.log(this.name + "「zzz」");
  }

  static makeChild(parent) {
    return new this(`${parent.name}ジュニア`);
  }
}

var parent = new Animal("ポチ");
parent.sleep(); // ポチ「zzz」

var child = Animal.makeChild(parent);
child.sleep(); // ポチジュニア「zzz」
```

このように、クラスに対して定義されたstaticメソッドはそのクラスオブジェクト自身のメソッドとなります。この例では`Animal.makeChild`としてこれを呼び出しています。staticメソッドとして定義されているメソッドは組み込みオブジェクトにも結構あります（`Array.from`とか）ので、使う機会があることでしょう。

## `Object.setPrototypeOf`
余談ですが、ES2015で追加されたprototype関係メソッドとして[Object.setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)があります。これはたいへんやばいメソッドであり、名前から分かる通り、オブジェクトのプロトタイプを後から書き換えることができるというものです。

```js
var myCat = new Cat("たま");

myCat.speak(); // たま「にゃーん」

// myCatのプロトタイプをAnimal.prototypeに書き換える
Object.setPrototypeOf(myCat, Animal.prototype);

myCat.speak(); // たま「鳴き声」
```

まあ何かの使い道があるから用意されたのでしょうが、これを使うのはとてもやばいので、本当に必要ない限りは避けたほうがよいでしょう。オブジェクトを作るときにプロトタイプが決まっているなら`Object.create`でいいですしね。

ちなみに、これを使ってプロトタイプが無限ループするオブジェクトを作成しようとするとエラーになります。偉いですね。

```js
var loopObj = {};
// loopObjのプロトタイプをloopObj自身にセットしようとするとエラー
Object.setPrototypeOf(loopObj, loopObj);
```

# 未来のJavaScriptにおけるオブジェクト指向
※ここからの内容はまだ確定していない言語仕様に関する記述です。今後大きく変化したり立ち消えになったりする可能性もありますので、ぜひ鵜呑みにせずにご自分で情報収集を行ってください。

JavaScript（というかECMAScript）の仕様策定においては将来JavaScriptに採用すべき言語機能の提案や議論がプロポーザルという形で公開されています。まだJavaScriptへの採用が決定していないものの、将来的に入りそうな機能というのはいくつかあります。その中でクラス関連のものを紹介します。

## [プライベートプロパティ](https://github.com/tc39/proposal-class-fields)・[プライベートメソッド](https://github.com/tc39/proposal-private-methods) (Stage 3)

今後のJavaScriptにおけるオブジェクト指向関連の流れは、最後に紹介した`class`構文を強化する方向で進んでいきそうです。その中で最も実現が近そうなのは**プライベートプロパティ・メソッド**でしょう。多くのクラスベース言語では、プロパティやメソッドをprivateとすることによってそれらを同クラス内からしか参照できないようにする機能があります。一方、JavaScriptはオブジェクトは所詮ただの連想配列＋αであるという立場からか、これまでそのような機能はありませんでした。しかし、ES2015でクラスベースっぽい構文を導入したことをきっかけにそのような機能に目を向けるようになったのでしょう。

プライベートプロパティは以下のように`#`を用いて宣言したりアクセスしたりします。

```js
class Animal {
  #age = 0;
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name}「鳴き声」`);
    this.#age++;
  }
}
```

これは1回鳴くごとに1歳年をとる変な動物のオブジェクトです。しかもプロパティ`#age`はプライベートプロパティなので、外から年を知ることはできず意味がありません。この`obj.#prop`というように`#`をつける構文は独特でなかなかびっくりさせられますね。プライベートなプロパティ名は普通のプロパティ名とは独立した名前空間を持っているようです。そのため`obj['#age']`のようにしても同じ意味にはなりません。

また、[staticフィールドが可能になったりstaticでprivateなメソッドも可能になったりしそうです](https://github.com/tc39/proposal-static-class-features/)。

その他の関連しそうなプロポーザルを列挙しますが、Stage 2以下なので何年先か分からないし大きく変わったり消えることも普通にありそうですから、そんなに期待せずにのんびり待ちましょう。競合しているやつもありますしね。

- [First-class protocols](https://github.com/michaelficarra/proposal-first-class-protocols): `class Foo implements SomeInterface { ... }`みたいな構文が入るやつです。
- [Maximally minimal mixins](https://github.com/justinfagnani/proposal-mixins): `mixin M { ... }`みたいな感じでmixinを定義して`class Foo with M { ... }`みたいな感じで使うやつです。
- [Class static block](https://github.com/tc39/proposal-class-static-block#readme):  `class`の定義時にstaticなスコープ内で初期化を実行できるやつです。
- [Class access expressions](https://github.com/tc39/proposal-class-access-expressions) `class`の定義内で`class`と書くとその`class`にアクセスできるやつです。

# まとめ
この記事では言語機能の説明に特化し、原始時代から未来に至るまでのJavaScriptにおけるオブジェクト指向のやり方を概観しました。これで、オブジェクト指向の概念を99%理解してオブジェクト指向をやってみたくなった皆さんも、とりあえずJavaScriptでオブジェクト指向を試すことができると思います。