概要
JavaScriptは プロトタイプベースの継承モデル を採用しており、クラスベースとは異なるメカニズムでオブジェクト同士の関係性が構築される。
この関係を支えるのが「プロトタイプチェーン」であり、以下のような現象を支配している:
- なぜオブジェクトに定義していない
toString()
が呼べるのか - 関数に自動的に
prototype
プロパティが存在する理由 -
__proto__
とprototype
の違い -
new
キーワードが内部的に何をしているのか
この記事では、プロトタイプチェーンの構造・設計・使い所・落とし穴を徹底的に整理する。
プロトタイプベースの継承とは?
すべてのオブジェクトは、別のオブジェクト(プロトタイプ)を参照している。
プロパティやメソッドが見つからなかった場合、その参照先をたどって探索が行われる。
この参照の連鎖が「プロトタイプチェーン」。
const obj = {};
obj.toString(); // ✅ Object.prototype.toString が参照されている
基本構造
const a = {};
console.log(a.__proto__ === Object.prototype); // true
-
a
自体はtoString
を持たない -
a.__proto__
(非推奨だが慣例的に使用) を辿るとObject.prototype
に到達 - 最終的に
Object.prototype.__proto__ === null
→ チェーンの終端
関数と prototype の関係
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};
const dog = new Animal('Pochi');
dog.speak(); // 'Pochi makes a sound'
✅ 内部的な流れ
1. Animal.prototype が dog.__proto__ に代入される
2. dog.speak() → dog.__proto__.speak() を参照
__proto__
と prototype
の違い
用語 | 意味 |
---|---|
obj.__proto__ |
インスタンスが参照するプロトタイプ |
Func.prototype |
関数コンストラクタが作るインスタンスの参照先 |
obj.constructor |
インスタンスを生成した関数(Func) |
dog.__proto__ === Animal.prototype; // ✅ true
Animal.prototype.constructor === Animal; // ✅ true
new
キーワードの内部的な動作
const d = new Animal('Toto');
内部的には以下の処理が行われている:
function newOperator(Cls, ...args) {
const instance = Object.create(Cls.prototype);
Cls.apply(instance, args);
return instance;
}
→ ✅ Object.create()
によりプロトタイプを接続
→ ✅ apply
によりインスタンスの初期化
プロトタイプチェーンの視覚イメージ
dog --> Animal.prototype --> Object.prototype --> null
継承の拡張:プロトタイプによる多段階継承
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function () {
return `${this.name} barks`;
};
const d = new Dog('Pochi');
d.speak(); // Pochi barks
クラス構文との接続点(ES6以降)
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
speak() {
return `${this.name} barks`;
}
}
const d = new Dog('Toto');
d.speak(); // Toto barks
→ ✅ この構文は糖衣構文であり、内部ではプロトタイプチェーンが構築されている
よくある誤解
❌ prototype
はインスタンスに存在しない
console.log(d.prototype); // undefined
→ ✅ prototype
は関数にしか存在しないプロパティ
❌ __proto__
は非標準(とはいえ使われがち)
→ ✅ 安全には Object.getPrototypeOf(obj)
を使う
設計におけるプロトタイプの使い所
目的 | 適切な設計手段 |
---|---|
複数のインスタンスで同一メソッドを共有したい | Func.prototype.method = ... |
クラスベースの継承を書きたい | ES6 class / extends 構文 |
プロトタイプチェーンを明示制御したい |
Object.create() / setPrototypeOf()
|
高速な共有メソッド設計 |
prototype ベースの設計 |
結語
JavaScriptのオブジェクト設計は、「クラスベースの模倣」ではない。
それはプロトタイプという連鎖構造により、オブジェクト間の振る舞いを合成する設計である。
-
__proto__
は参照先 -
prototype
は生成元の青写真 -
new
は両者をつなぐトリガー
構造を理解すれば、継承の挙動は“必然的”に説明できるようになる。
クラス構文に頼る前に、プロトタイプという“原点”を設計として理解せよ。