概要: このチュートリアルでは、JavaScriptのプロトタイプとその内部動作について学習します。
JavaScriptプロトタイプ入門
JavaScriptでは、オブジェクトはプロトタイプを介して互いに機能を継承できます。すべてのオブジェクトは、prototypeと呼ばれる独自の プロパティ を持っています。
prototype自体も別のオブジェクトであるため、prototypeには独自のprototypeがあります。これは、プロトタイプチェーンと呼ばれるものを形成します。プロトタイプチェーンは、プロトタイプの独自のプロトタイプがnullになったときに終了します。
nameというプロパティを持つオブジェクトpersonがあるとします。
let person = {'name' : 'John'}Code language: JavaScript (javascript)コンソールでpersonオブジェクトを調べると、personオブジェクトには[[Prototype]]で表されるprototypeというプロパティがあることがわかります。

プロトタイプ自体も、独自の プロパティ を持つオブジェクトです。

オブジェクトのプロパティにアクセスすると、オブジェクトにそのプロパティがある場合、プロパティ値が返されます。次の例では、personオブジェクトのnameプロパティにアクセスしています。

期待どおりにnameプロパティの値が返されます。
ただし、オブジェクトに存在しないプロパティにアクセスすると、JavaScriptエンジンはオブジェクトのプロトタイプを検索します。
JavaScriptエンジンがオブジェクトのプロトタイプでプロパティを見つけられない場合、プロパティが見つかるかプロトタイプチェーンの最後に到達するまで、プロトタイプのプロトタイプを検索します。
たとえば、personオブジェクトのtoString()メソッドは次のように呼び出すことができます。

toString()メソッドは、personオブジェクトの文字列表現を返します。デフォルトでは、[object Object]であり、これは明確ではありません。
関数がオブジェクトのプロパティの値である場合、それはメソッドと呼ばれます。したがって、メソッドは関数を値とするプロパティです。
この例では、personオブジェクトでtoString()メソッドを呼び出すと、JavaScriptエンジンはそれをpersonオブジェクト内で見つけます。
personオブジェクトにはtoString()メソッドがないため、personのプロトタイプオブジェクトでtoString()メソッドを検索します。
personのプロトタイプにはtoString()メソッドがあるため、JavaScriptはpersonのプロトタイプオブジェクトのtoString()を呼び出します。

JavaScriptプロトタイプの図解
JavaScriptには組み込みのObject()関数があります。Object関数を渡すと、typeof演算子は'function'を返します。例えば
typeof(Object)Code language: JavaScript (javascript)出力
'function'Code language: JavaScript (javascript)Object()はオブジェクトではなく関数であることに注意してください。JavaScriptのプロトタイプについて初めて学習する場合、これは混乱を招く可能性があります。
また、JavaScriptは、Object()関数のprototypeプロパティを介して参照できる匿名オブジェクトを提供します。
console.log(Object.prototype);Code language: JavaScript (javascript)Object.prototypeオブジェクトには、toString()やvalueOf()など、いくつかの便利なプロパティとメソッドがあります。
Object.prototypeには、Object()関数を 参照 する重要なプロパティであるconstructorもあります。
次のステートメントは、Object.prototype.constructorプロパティがObject関数を 参照 していることを確認します。
console.log(Object.prototype.constructor === Object); // trueCode language: JavaScript (javascript)円が関数を表し、正方形がオブジェクトを表すとします。次の図は、Object()関数とObject.prototypeオブジェクトの関係を示しています。
まず、Personと呼ばれるコンストラクタ関数を次のように定義します。
function Person(name) {
this.name = name;
}Code language: JavaScript (javascript)この例では、Person()関数はname引数を受け取り、それをthisオブジェクトのnameプロパティに代入します。
舞台裏では、JavaScriptは新しい関数Person()と匿名オブジェクトを作成します。
Object()関数と同様に、Person()関数には、匿名オブジェクトを 参照 するprototypeと呼ばれるプロパティがあります。匿名オブジェクトには、Person()関数を 参照 するconstructorプロパティがあります。
以下は、Person()関数とPerson.prototypeによって 参照 される匿名オブジェクトを示しています。
console.log(Person);
console.log(Person.prototype);Code language: CSS (css)さらに、JavaScriptは、プロトタイプリンケージとして知られる[[Prototype]]を介して、Person.prototypeオブジェクトをObject.prototypeオブジェクトにリンクします。
プロトタイプリンケージは、次の図では[[Prototype]]で表されています。
JavaScriptプロトタイプオブジェクトでのメソッドの定義
以下は、Person.prototypeオブジェクトにgreet()と呼ばれる新しいメソッドを定義しています。
Person.prototype.greet = function() {
return "Hi, I'm " + this.name + "!";
}Code language: JavaScript (javascript)この場合、JavaScriptエンジンはgreet()メソッドをPerson.prototypeオブジェクトに追加します。
以下は、Personの新しいインスタンスを作成します。
let p1 = new Person('John');Code language: JavaScript (javascript)内部的には、JavaScriptエンジンはp1という名前の新しいオブジェクトを作成し、プロトタイプリンケージを介してp1オブジェクトをPerson.prototypeオブジェクトにリンクします。
p1、Person.prototype、およびObject.protoype間のリンクは、プロトタイプチェーンと呼ばれます。
以下は、p1オブジェクトでgreet()メソッドを呼び出します。
let greeting = p1.greet();
console.log(greeting);Code language: JavaScript (javascript)p1にはgreet()メソッドがないため、JavaScriptはプロトタイプリンケージをたどり、Person.prototypeオブジェクトで見つけます。
JavaScriptはPerson.prototypeオブジェクトでgreet()メソッドを見つけることができるため、greet()メソッドを実行し、結果を返します。
以下は、p1オブジェクトでtoString()メソッドを呼び出します。
let s = p1.toString();
console.log(s);Code language: JavaScript (javascript)この場合、JavaScriptエンジンはプロトタイプチェーンをたどり、Person.prototypeでtoString()メソッドを探します。
Person.prototypeにはtoString()メソッドがないため、JavaScriptエンジンはプロトタイプチェーンを上がり、Object.prototypeオブジェクトでtoString()メソッドを検索します。
JavaScriptはObject.prototypeでtoString()メソッドを見つけることができるため、toString()メソッドを実行します。
Person.prototypeオブジェクトとObject.prototypeオブジェクトに存在しないメソッドを呼び出すと、JavaScriptエンジンはプロトタイプチェーンをたどり、メソッドが見つからない場合はエラーをスローします。例えば
p1.fly();
Code language: CSS (css)fly()メソッドはプロトタイプチェーンのどのオブジェクトにも存在しないため、JavaScriptエンジンは次のエラーを発行します。
TypeError: p1.fly is not a functionCode language: JavaScript (javascript)以下は、nameプロパティが'Jane'であるPersonの別のインスタンスを作成します。
let p2 = new Person('Jane');Code language: JavaScript (javascript)p2オブジェクトは、p1オブジェクトと同じプロパティとメソッドを持っています。
結論として、prototypeオブジェクトでメソッドを定義すると、このメソッドはすべてのインスタンスで共有されます。
個々のオブジェクトでのメソッドの定義
以下は、p2オブジェクトでdraw()メソッドを定義しています。
p2.draw = function () {
return "I can draw.";
};
Code language: JavaScript (javascript)JavaScriptエンジンは、draw()メソッドをPerson.prototypeオブジェクトではなく、p2オブジェクトに追加します。
つまり、p2オブジェクトでdraw()メソッドを呼び出すことができます。
p2.draw();Code language: CSS (css)ただし、p1オブジェクトでdraw()メソッドを呼び出すことはできません。
p1.draw()Code language: CSS (css)エラー
TypeError: p1.draw is not a functionCode language: JavaScript (javascript)オブジェクトでメソッドを定義すると、そのメソッドはそのオブジェクトでのみ使用できます。デフォルトでは、他のオブジェクトと共有することはできません。
プロトタイプリンケージの取得
__proto__はダンダープロトと発音します。 __proto__は、Object.prototypeオブジェクトのアクセサプロパティです。これは、アクセスされるオブジェクトの内部プロトタイプリンケージ([[Prototype]])を公開します。
__proto__は、Webブラウザとの互換性を確保するためにES6で標準化されています。ただし、将来的にはObject.getPrototypeOf()に取って代わられる可能性があります。したがって、本番コードでは__proto__を決して使用しないでください。
p1.__proto__は、Person.prototypeオブジェクトを 参照 する[[Prototype]]を公開します。
同様に、p2.__proto__もp1.__proto__と同じオブジェクトを 参照 します。
console.log(p1.__proto__ === Person.prototype); // true
console.log(p1.__proto__ === p2.__proto__); // trueCode language: JavaScript (javascript)前述のように、__proto__の代わりにObject.getPrototypeOf()メソッドを使用する必要があります。 Object.getPrototypeOf()メソッドは、指定されたオブジェクトのプロトタイプを返します。
console.log(p1.__proto__ === Object.getPrototypeOf(p1)); // trueCode language: JavaScript (javascript)プロトタイプリンケージを取得するもう1つの一般的な方法は、Object.getPrototypeOf()メソッドがconstructorプロパティを介して使用できない場合、次のようにすることです。
p1.constructor.prototypeCode language: CSS (css)p1.constructorはPersonを返すため、p1.constructor.prototypeはプロトタイプオブジェクトを返します。
シャドーイング
次のメソッド呼び出しを参照してください。
console.log(p1.greet());Code language: CSS (css)p1オブジェクトにはgreet()メソッドが定義されていないため、JavaScriptはプロトタイプチェーンを上がり、それを見つけます。この場合、Person.prototypeオブジェクトでメソッドを見つけることができます。
Person.prototypeオブジェクトのメソッドと同じ名前の新しいメソッドをオブジェクトp1に追加してみましょう。
p1.greet = function() {
console.log('Hello');
}Code language: JavaScript (javascript)そして、greet()メソッドを呼び出します。
console.log(p1.greet());Code language: CSS (css)p1オブジェクトにはgreet()メソッドがあるため、JavaScriptはプロトタイプチェーンで検索することなく、すぐに実行します。
これはシャドーイングの例です。 p1オブジェクトのgreet()メソッドは、p1オブジェクトが 参照 するprototypeオブジェクトのgreet()メソッドをシャドーイングします。
まとめ
Object()関数には、Object.prototypeオブジェクトを 参照 するprototypeと呼ばれるプロパティがあります。Object.prototypeオブジェクトには、toString()やvalueOf()など、すべてのオブジェクトで使用可能なすべてのプロパティとメソッドがあります。Object.prototypeオブジェクトには、Object関数を 参照 するconstructorプロパティがあります。- すべての関数には
prototypeオブジェクトがあります。このプロトタイプオブジェクトは、[[prototype]]リンケージまたは__proto__プロパティを介してObject.prototypeオブジェクトを 参照 します。 - プロトタイプチェーンにより、1つのオブジェクトは
[[prototype]]リンケージを介してそのprototypeオブジェクトのメソッドとプロパティを使用できます。 Object.getPrototypeOf()メソッドは、指定されたオブジェクトのプロトタイプオブジェクトを返します。__proto__の代わりにObject.getPrototypeOf()メソッドを使用してください。

