概要: このチュートリアルでは、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); // true
Code 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 function
Code 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 function
Code 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__); // true
Code language: JavaScript (javascript)
前述のように、__proto__
の代わりにObject.getPrototypeOf()
メソッドを使用する必要があります。 Object.getPrototypeOf()
メソッドは、指定されたオブジェクトのプロトタイプを返します。
console.log(p1.__proto__ === Object.getPrototypeOf(p1)); // true
Code language: JavaScript (javascript)
プロトタイプリンケージを取得するもう1つの一般的な方法は、Object.getPrototypeOf()
メソッドがconstructor
プロパティを介して使用できない場合、次のようにすることです。
p1.constructor.prototype
Code 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()
メソッドを使用してください。