JavaScript(以後JSと書きます)を学習していく中で、Prototypeという言葉を目にすることがあると思います。
JSでは、オブジェクトの継承やメソッドの共有を実現する仕組みとしてPrototypeが採用されています。
今回、Prototypeについて学ぶ機会があったため、備忘録も兼ね初学者向けに記事を書いていこうと思います!
なぜPrototypeが必要なのか
下記コードを見てください。
const user1 = {
name: "Taro",
hello() {
console.log("Hello");
},
};
const user2 = {
name: "Bob",
hello() {
console.log("Hello");
},
};
user1、user2それぞれのオブジェクトでhelloメソッドがあります。
メソッドや変数はメモリ上に保持されます。
同じ内容のメソッドをオブジェクトごとに作成すると、その分だけメモリを消費してしまいます。
もし、user3、user4と増えていき、user1000となった場合、1000個のオブジェクトそれぞれに同じhello()メソッドが存在することになります。
JavaScriptでは、この問題を解決するための仕組みが用意されています!
それがPrototypeです!
Prototypeとは?
Prototypeを説明すると、「複数のオブジェクトで共通して利用するプロパティやメソッドをまとめて保持しておく場所」です。
すなわち、「共有場所のこと」ですね!
先ほどのhello()メソッドを、それぞれのオブジェクトが持つのではなく、Prototypeに1つだけ用意しておけば、すべてのオブジェクトから利用できます。
下記の図を見てください。
この仕組みにより、helloメソッドは一つだけ作成され、複数のオブジェクトで共有できるため、メモリ効率が良くなります!
Prototype Chain
実際にPrototypeの使用例を見てみましょう!
次のようなオブジェクトがあるとします。
const user = {
name: "Taro",
};
user.nameのようにプロパティへアクセスしたとき、JavaScriptはまずuserオブジェクト自身の中を探します。
見つからなかった場合、JavaScriptは、自動的にPrototypeの中を探しにいきます。
この「見つからなければPrototypeを順番に探していく仕組み」をPrototype Chain(プロトタイプチェーン)と呼びます。
下記のようにオブジェクト自体に存在しないメソッドが利用できるのもプロトタイプチェーンの仕組みのおかげです。
const user = {
name: "Taro",
};
console.log(user.toString());//エラーにならない!
[[Prototype]]と__proto__って何?
[[Prototype]]とは
上の図の中にも度々登場した[[Prototype]]ですが、なんだこれはと思った方もいると思います。
実は、JavaScriptのすべてのオブジェクトは、内部に[[Prototype]]という特別な内部スロットを持っています。
内部スロット:開発者が直接操作できない、JavaScriptエンジンが内部で管理している情報のこと。
この[[Prototype]]には、「次に探しに行くオブジェクト」の参照が保存されています!
user
↓
[[Prototype]]
↓
Object.prototype
//こんな感じで、JavaScriptが自動で辿ります。
そのため、[[Prototype]]はJavaScriptエンジンだけが扱う内部スロットであり、通常のコードから直接アクセスすることはできません。
__proto__とは
もしかするとブラウザのコンソールで「__proto__」という表記を見たことがあるかもしれません!
__proto__は、内部スロットである[[Prototype]]を取得・設定するためにObject.prototypeに用意されているアクセサプロパティです!
アクセサプロパティ:値を直接保持するのではなく、取得(getter)や設定(setter)の処理を実行するためのプロパティです。
そのため、下記のようなコードで[[Prototype]]を取得できます!
console.log(user.__proto__);
//現在では下記を使うのが推奨されています。
Object.getPrototypeOf(user);
この__proto__ですが、オブジェクト自身は持っていません!
実は、Object.prototypeに定義されているアクセサプロパティです!
Object.prototype
│
├── toString()
├── hasOwnProperty()
└── __proto__ ← ここにある
こんな感じです!
user.__proto__を実行すると、内部的には次のような流れになります!
user.__proto__
- user自身に
__proto__があるか探す - 無い
- Object.prototypeへ移動
-
__proto__アクセサを見つける - そのアクセサがuserの[[Prototype]]を返す
という流れになります。
実際に確認する方法として、「hasOwnProperty()」というオブジェクト自身がプロパティを持っているか確認するメソッドを実行すると「false」が返ってきます!
const user = {
name: "Taro",
};
console.log(user.hasOwnProperty("__proto__"));
//falseが返ってくる!
hasOwnProperty()ではfalseになりますが、in演算子ではtrueになります!
これは、__proto__がuser自身ではなく、プロトタイプチェーン上のObject.prototypeに存在するためです。
console.log("__proto__" in user);
// true
つまり、user自身は__proto__を持っていません。
ここまでの違いをまとめると次のようになります。
| 項目 | [[Prototype]] |
__proto__ |
|---|---|---|
| 全てのオブジェクトが持つか | 持つ | 直接持たない |
| 種類 | 内部スロット | アクセサプロパティ |
| JavaScriptから直接アクセス | 不可 | 可能だが非推奨 |
| 説明 | 各オブジェクトが必ず持っている内部スロット。(JavaScriptエンジンだけが扱える) | [[Prototype]]を取得・設定するためのプロパティ |
まとめ
ここまでで、次の内容を解説しました。
| 用語 | 説明 |
|---|---|
| Prototype | 共通して利用するプロパティやメソッドを保持できるオブジェクト |
| Prototype Chain | プロパティが見つからない場合にPrototypeを順番に探す仕組み |
[[Prototype]] |
Prototype Chainをたどるために各オブジェクトが持つ内部スロット |
__proto__ |
[[Prototype]]を取得・設定するためのアクセサプロパティ |
JavaScriptでは、オブジェクト自身に存在しないプロパティやメソッドでも、プロトタイプチェーンをたどることで利用できる場合があります。
しかし、JavaScriptにはもう一つ紛らわしい名前があります。
それが「prototype」です。
prototypeは関数だけが持つ特別なプロパティで、new演算子やクラス構文を理解するうえで欠かせない存在です。
次回は、もう一つの重要な概念である prototype プロパティについて、クラスや new 演算子の仕組みと合わせて解説する予定です!」


