Contents
1. プロトタイプチェーンとは
オブジェクトは内部スロット [[Prototype]](ES2022 では prototype)に別のオブジェクトへの参照を持ちます。この参照をたどってプロパティ検索が行われる仕組みを プロトタイプチェーン と呼びます。
- 最上位は
nullを指すオブジェクトで、ここまで到達すると検索は終了します。 - 実装例:
dog.__proto__ → animal.__proto__ → Object.prototype.__proto__ → null
MDN の解説(Prototype chain and inheritance)や ECMA‑262 仕様(#sec-ordinary-object-internal-methods-and-internal-slots-prototype)を参照すると、内部的に「[[Get]]」アルゴリズムが再帰的に呼び出されることが分かります。
|
1 2 |
dog ──> animal ──> Object.prototype ──> null |
dog.bark() が見つからない場合、エンジンは自動的に animal を検索し、最終的に Object.prototype まで遡ります。
2. [[Prototype]] と __proto__ の違い
| 項目 | 説明 |
|---|---|
[[Prototype]] |
仕様上の内部スロットで、直接操作できません。ECMAScript のオブジェクトは必ずこのスロットを持ちます(§9.1.2)。 |
__proto__ |
デバッグや非標準的操作のためにブラウザが実装したアクセサです。MDN では「非推奨」ではなく「互換性のためだけに残っている」旨が記載されています(Object.prototype.proto)。 |
| 正式な取得・設定手段 | Object.getPrototypeOf(obj) / Object.setPrototypeOf(obj, proto) が標準です。 |
実務上の指針
- 参照はObject.getPrototypeOf/Object.setPrototypeOfを使う。
-obj.__proto__はコンソールでの確認以外では使用しない。
3. Function コンストラクタで安全に継承する
3.1 基本パターン(Animal → Dog)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// ----- Parent (Animal) ----- function Animal(name, foods) { this.name = name; // インスタンスごとに配列をコピー this.favoriteFoods = foods ? [...foods] : []; } Animal.prototype.speak = function () { console.log(`${this.name} が鳴きます`); }; // ----- Child (Dog) ----- function Dog(name, foods, breed) { Animal.call(this, name, foods); // 親のコンストラクタ実行 this.breed = breed; } // 継承設定(prototype のみをコピー) Dog.prototype = Object.create(Animal.prototype); Object.defineProperty(Dog.prototype, "constructor", { value: Dog, enumerable: false, writable: true, }); // 子固有メソッド Dog.prototype.fetch = function (item) { console.log(`${this.name} が ${item} を取ってきました`); }; |
ポイントは Object.create(Animal.prototype) により、親コンストラクタの副作用を排除しつつプロトタイプだけを継承できる点です。
3.2 共有参照型プロパティが招くバグと回避策
|
1 2 3 4 5 6 7 8 |
function Cat() {} Cat.prototype.tricks = []; // ← すべてのインスタンスで同一配列 const c1 = new Cat(); c1.tricks.push("jump"); const c2 = new Cat(); console.log(c2.tricks); // ["jump"] と予期せぬ共有が発生 |
回避策は、参照型(配列・オブジェクト)をコンストラクタ内部で生成することです。
|
1 2 3 4 5 |
function Cat(name) { this.name = name; this.tricks = []; // インスタンスごとに新しい配列 } |
ベストプラクティス
-prototypeにはメソッドだけを置く。
- 参照型は必ずコンストラクタで初期化する。
4. Object.create を活用した継承パターン
4.1 Object.create の利点
- 副作用がない:親コンストラクタを呼び出さずにプロトタイプチェーンだけを構築。
- メモリ効率が高い:不要なインスタンスフィールドが作られません。
- 可読性が向上:継承関係が一目で分かります。
MDN の解説(Object.create())でも「純粋なプロトタイプオブジェクトを生成」する手段として推奨されています。
4.2 実装例(Animal → Cat)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// ----- Parent (Animal) ----- function Animal(name) { this.name = name; } Animal.prototype.eat = function (food) { console.log(`${this.name} は ${food} を食べました`); }; // ----- Child (Cat) ----- function Cat(name, lives) { Animal.call(this, name); // インスタンス固有プロパティの初期化 this.lives = lives; } // 継承設定 Cat.prototype = Object.create(Animal.prototype); Object.defineProperty(Cat.prototype, "constructor", { value: Cat, enumerable: false, writable: true, }); // 子クラス独自メソッド Cat.prototype.meow = function () { console.log(`${this.name} がニャーと鳴きました`); }; |
チェーンの可視化
|
1 2 3 4 |
console.dir(Cat.prototype); // → [[Prototype]]: Animal.prototype // __proto__: Object.prototype |
5. ES6 class 構文と内部的なプロトタイプ継承
5.1 class が行う処理
ES2022 の仕様(#sec-class-definitions)によれば、次のように変換されます。
|
1 2 3 4 5 6 7 8 |
// ES6 class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } } |
は概念的に以下と同等です。
|
1 2 3 4 5 6 7 |
function Dog(name, breed) { Animal.call(this, name); this.breed = breed; } Dog.prototype = Object.create(Animal.prototype); Object.setPrototypeOf(Dog, Animal); // 静的継承(クラスメソッドの継承) |
class は構文糖衣であり、内部では Object.create と Object.setPrototypeOf が実行されます。
5.2 super() の正しい使い方
super()はコンストラクタの最初に呼び出す(それ以前にthisを参照するとReferenceError)。- 親クラスでインスタンス固有の配列などを初期化する場合は、
super()後に子側で追加プロパティを設定します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Animal { constructor(name) { this.name = name; this.traits = []; // インスタンスごとの配列 } } class Dog extends Animal { constructor(name, breed) { super(name); // ← 必ず先頭で呼び出す this.breed = breed; // 親の初期化後に設定 } bark() { console.log(`${this.name} がワンワン`); } } |
6. デバッグ手法と継承スタイル選択基準
6.1 プロトタイプチェーンを確認する方法
|
1 2 3 4 5 6 |
const d = new Dog('ポチ', '柴犬'); console.dir(d); // Chrome DevTools で [[Prototype]] が表示される console.log(Object.getPrototypeOf(d) === Dog.prototype); // true console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true |
Object.getPrototypeOf は標準 API(MDN: Object.getPrototypeOf)であり、__proto__ より安全です。
6.2 継承スタイルの比較表
| スタイル | 実行速度 | メモリ使用量 | デバッグ容易性 | 推奨度 |
|---|---|---|---|---|
Function + Object.create |
高 | 高 | 中 | ✅ |
ES6 class |
高(エンジン最適化) | 高 | 高 | ✅ |
prototype = new Parent() |
低(副作用あり) | 中 | 低 | ❌ |
結論
- ES6+ が利用可能なプロジェクトは class を採用すれば可読性と保守性が向上します。
- レガシー環境や細かい prototype 操作が必要な場合は、Object.create と関数コンストラクタの組み合わせが安全です。
まとめ(約230文字)
- プロトタイプチェーンは
[[Prototype]]の連鎖で属性検索を行い、正式取得はObject.getPrototypeOf。 - Function コンストラクタでも
Object.createとParent.call(this)を組み合わせれば副作用のない継承が実現できる。 Object.createは余計なコンストラクタ実行を防ぎ、メモリ効率が高いため推奨される手法である。- ES6
classは内部的に同様の prototype 継承をラップし、可読性・保守性から主流になる。 - デバッグは
console.dirとObject.getPrototypeOfを併用し、プロジェクト要件と環境に応じて継承スタイルを選択することがベストプラクティスである。