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