🐆

【JavaScript】プロトタイプオブジェクトとは?

2022/09/24に公開

プロトタイプを理解するために次のコードを見てください

const obj = {};
console.log(obj.toString()); // => "[object Object]"

なぜ空っぽのオブジェクトなのにtoStringメソッドを利用できるのでしょうか。それは{}がObject.prototypeプロパティがもつprotypeオブジェクトを継承しているからです。実はほとんどすべてのオブジェクトはprototypeオブジェクトを継承しています。それは以下のようにして確認することができます。

const str = "";
const num = 1;
const fn = ()=>{};
const arr = [];
const obj = {};

console.log(str.__proto__.__proto__); // => {}
console.log(num.__proto__.__proto__); // => {}
console.log(fn.__proto__.__proto__); // => {}
console.log(arr.__proto__.__proto__); // => {}
console.log(obj.__proto__); // => {}

__proto__を使うことで自身の元となったものを確認できます。上記に書いた通り、すべてのオブジェクトはprototypeオブジェクトを持ったObjectを継承、つまり元として作成されているので__proto__をつなげていくとObjectにたどり着きます。
そしてObjectにアクセスするとtoStringメソッドが確認できます。

console.log(str.__proto__.__proto__.toString()); // => [object Object]

このように__proto__を利用してprototypeプロパティにあるメソッドを参照する仕組みをプロトタイプチェーンと呼びます。

ちなみにObjectの__proto__はnullになります。

console.log(num.__proto__.__proto__.__proto__); // => null

図で見る参照の流れ

この項目では変数strがプロトタイプチェーンでtoStringメソッドを参照する流れを確認します。

const str = "";
console.log(str.__proto__.__proto__.toString());

まずは次の図を見てください。

親クラスと記載しているものがObjectだと思ってください。そしてObjectはprototypeプロパティを持っており、その中にメソッドAを持っています。前項の話題を借りてAメソッドはtoStringメソッドとして進めます。
ではObjectを継承して子クラスを作成しましょう。子クラスには自動的に__proto__というプロパティが作成されます。この__proto__は先ほど出てきた__proto__と同じです。__proto__は親クラスのprototypeへの参照を持っています。そして子クラスにメソッドを定義します。定義したメソッドは親クラスと同様自身が持つprototypeプロパティに格納されます。最後に子クラスからインスタンスBを生成します。インスタンスBは__proto__を持っています。

次に上記で書いてきた親クラスからBインスタンスを生成するまでの流れをコード上で確認していきます。子クラスには、Objectを継承しているStringを使って説明します。

Stringについて

Stringは文字列プリミティブのラッパーオブジェクトです。実は文字列のリテラルはJSによって自動的にラッパーオブジェクトに変換されています。この仕組みのおかげでリテラルの文字列でtoStringメソッドが利用できるようになっています。

const str = "test";
str.toString() // <=なぜか利用できるJSが裏側で変換
const str = new String("test");
str.toString() // <= StringはObjectオブジェクトを継承しているためtoStringが利用できる。

詳細はMDNを参照
https://developer.mozilla.org/ja/docs/Glossary/String

ではインスタンスB(Stringから生成したインスタンスを格納する変数str)からtoStringを呼び出してみます。

const str = new String("test");
str.toString();

問題なくできました。このとき、変数strは内部で自身の__proto__を確認しに行きます。__proto__を見ることはStringクラスのprototypeへの参照を意味します。ここでもう一度先ほどの図を確認してみましょう。

しかし、StringにはtoStringメソッドは定義されていません。そこで次にStringクラスの__proto__を確認します。Stringクラスの__proto__を参照することはObjectのprototypeを参照することを意味します。そこでやっとtoStringを発見することができたので変数strはtoStringメソッドを呼び出すことができます。

const str = new String("test");
console.log(str.__proto__.__proto__.toString);

Stringクラスから辿ると次のようになります。

console.log(String.__proto__.toString);

ちなみに、StringクラスにtoStringメソッドを定義すると先にそちらを参照するので、ObjectのtoStringが呼び出されることはありません。

用語

  • prototypeをたどることで参照できるメソッドをプロトタイプメソッドという。(toStringなど)
  • たどることのできない継承されないメソッドを静的メソッドという。(Object.keysなど)

参照

https://jsprimer.net/basic/prototype-object/

Discussion