JavaScriptのオブジェクトは、別のオブジェクトとプロトタイプと呼ばれる関係性を持つことができる。
あるオブジェクトをプロトタイプとした新しいオブジェクトの作り方
あるオブジェクトをプロトタイプとした新しいオブジェクトを作るにはObject.create()を使う。
const prototype = { a: 'apple' };
const obj = Object.create(prototype);
このとき、「prototypeはobjのプロトタイプである」とか「objはprototypeを継承している」とか言う。
プロトタイプの参照方法
オブジェクトのプロトタイプを参照する方法は2つある。
obj.__proto__Object.getPrototypeOf(obj)
前者の__proto__は非推奨であり、後者のObject.getPrototypeOf()を使うべきとされている。しかし前者は表記が簡潔なので、プロトタイプの説明ではよく使われる。
const prototype = { a: 'apple' };
const obj = Object.create(prototype);
console.log(obj.__proto__ === prototype); // => true
プロトタイプのプロパティの参照方法
プロトタイプのプロパティ(メソッドも含む)は、自身のプロパティのように参照できる。
const prototype = { a: 'apple' };
const obj = Object.create(prototype);
console.log(obj.a); // => "apple"
オブジェクトリテラルで作成したオブジェクトのプロトタイプ
オブジェクトリテラル({ ... })でオブジェクトを作成すると、そのオブジェクトはObject.prototypeを継承する。
const obj = { a: 'apple' };
console.log(obj.__proto__ === Object.prototype); // => true
Object.prototypeは.toString()などのメソッドを持つので、オブジェクトリテラルで作成したオブジェクトは、自身のメソッドのようにそれらを使うことができる。
const obj = { a: 'apple' };
console.log(obj.toString()); // => "[object Object]"
new演算子で作成したオブジェクトのプロトタイプ
例えばconst date = new Date();とすると、dateはDate.prototypeを継承する。
const date = new Date();
console.log(date.__proto__ === Date.prototype); // => true
このように、new演算子でnew Foo()として作成したオブジェクトは、普通はFoo.prototypeを継承する。(Fooの作りによっては、そうではないこともあり得る。)
備考:クラス構文のメソッド定義
JavaScriptのクラス構文で定義したメソッドは、そのクラスの.prototypeのメソッドになる。ただし、staticキーワードで定義したメソッドは、そのクラスのメソッド(静的メソッド)になる。
class Foo {
constructor() {
}
method1() {
}
method2() {
}
static method3() {
}
}
console.log(Object.getOwnPropertyNames(Foo.prototype)); // => ["constructor", "method1", "method2"]
console.log(Object.getOwnPropertyNames(Foo)); // => ["length", "prototype", "method3", "name"]
配列のプロトタイプ
JavaScriptでは、配列もオブジェクトである。
配列はArray.prototypeを継承している。よって、配列は自身のメソッドのようにArray.prototypeのメソッドを使える。
const arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // => true
console.log(arr.join('-')); // => "1-2-3"
プロトタイプチェーン
配列のプロトタイプはArray.prototypeである。そして実はArray.prototypeにもプロトタイプがあり、それはObject.prototypeである。
const arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // => true
console.log(arr.__proto__.__proto__ === Object.prototype); // => true
このようなプロトタイプの連鎖のことを、プロトタイプチェーンと呼ぶ。
プロトタイプのプロパティは自身のプロパティのように参照できことは説明したが、プロトタイプのプロトタイプのプロパティも同様に、自身のプロパティのように参照できる。
例えば次のようにすると、arrのプロトタイプであるArray.prototypeのプロトタイプであるObject.prototypeの.valueOf()が呼ばれる。
const arr = [1, 2, 3];
console.log(arr.valueOf());
あるオブジェクトのプロパティを参照しようとするとき、オブジェクトがそのプロパティを持っていればそれが返される。なければ、プロトタイプチェーンを辿っていき、プロパティが見つかった時点でそれが返される。
ちなみに、Object.prototypeのプロトタイプはnullである。
console.log(Object.prototype.__proto__ === null); // => true
プロトタイプチェーンの終端
プロパティを探すためにプロトタイプチェーンを辿っていき、nullに行き着いたらそこで探索は終わり、undefinedが返される。
例えば次のようにすると、arrも、arrのプロトタイプであるArray.prototypeも、Array.prototypeのプロトタイプであるObject.prototypeもhelloプロパティを持たず、Object.prototypeのプロトタイプはnullなので、undefinedが返る。
const arr = [1, 2, 3];
console.log(arr.hello); // => undefined