JavaScriptのプロトタイプチェーンを理解しよう(オブジェクト継承の仕組み)初心者にもわかるやさしい解説
生徒
「JavaScriptのプロトタイプチェーンって何ですか?よく聞くけど難しそうで…」
先生
「プロトタイプチェーンは、JavaScriptのオブジェクトが『他のオブジェクトの機能を使える仕組み』のことです。難しく聞こえますが、簡単に言うと『お手本から性質を受け継ぐこと』なんですよ。」
生徒
「お手本から性質を受け継ぐ?それはどういう意味ですか?」
先生
「例え話を交えて説明しますね。まずは基本から見ていきましょう!」
1. プロトタイプチェーンとは?オブジェクトの継承の仕組み
JavaScriptのオブジェクトは、他のオブジェクトをお手本(プロトタイプ)として持つことができ、そのお手本が持っている性質や動作(プロパティやメソッド)を使うことができます。この仕組みをプロトタイプチェーンと言います。
このチェーンは、オブジェクトが持っていないものを順番に「親」のオブジェクトに探しにいくことを意味しています。
2. 例え話:プロトタイプチェーンはお手本を借りる仕組み
例えば、あなたが絵を描くときに、教科書の絵を参考にして描くとします。教科書の絵がプロトタイプで、あなたの絵がオブジェクトです。
もしあなたの絵に「色」がなければ、教科書の絵を見て「色」を使います。これがプロトタイプチェーンのイメージです。オブジェクトは自分になければ、プロトタイプをたどって探しにいくのです。
3. JavaScriptでプロトタイプチェーンを見る簡単な例
const animal = {
eats: true,
walk() {
console.log("歩いています");
}
};
const rabbit = {
jumps: true,
};
rabbit.__proto__ = animal; // rabbitのプロトタイプをanimalに設定
console.log(rabbit.eats); // true(rabbitには無いがanimalから継承)
rabbit.walk(); // 「歩いています」と表示
console.log(rabbit.jumps); // true(rabbit自身のプロパティ)
この例では、rabbitはanimalをプロトタイプとして持ち、animalの性質や動作を使うことができます。
4. __proto__とは?プロトタイプを指す特別なプロパティ
__proto__はオブジェクトが持つ「このオブジェクトの親はどれか?」を示す特別なプロパティです。これによってプロトタイプチェーンができています。
ただし、__proto__は古い書き方で、通常はObject.getPrototypeOf()やObject.setPrototypeOf()を使うことが推奨されています。
5. new演算子とプロトタイプチェーンの関係
JavaScriptでnew演算子を使うと、新しいオブジェクトが作られますが、そのオブジェクトは作った関数のプロトタイプを親として持ちます。
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log("こんにちは、" + this.name + "さん!");
};
const taro = new Person("太郎");
taro.greet(); // 「こんにちは、太郎さん!」と表示
ここでtaroはPerson.prototypeを親に持ち、greetメソッドを使えます。
6. プロトタイプチェーンがあるからできること
- 同じ種類のオブジェクトで共通の機能を持たせることができる。
- メモリを節約しながら便利な機能を共有できる。
- オブジェクト同士で情報や動作を受け継ぐことができる(継承の仕組み)。
この仕組みはJavaScriptの基本的で重要な考え方なので、しっかり理解しましょう。
7. プロトタイプチェーンの確認方法
オブジェクトのプロトタイプを調べたいときは、Object.getPrototypeOf()を使います。
console.log(Object.getPrototypeOf(rabbit) === animal); // true
このように、どのオブジェクトを親に持っているか調べられます。
8. 注意点:プロトタイプチェーンは無限に続くわけではない
プロトタイプチェーンは最後に必ずnullという特別な値で終わります。nullは「これ以上親がいません」という意味です。
console.log(Object.getPrototypeOf(animal)); // nullでないなら更に親がいる
これで探し物をどこまでも続ける無限ループにならず、止まることができます。
9. プロトタイプチェーンのまとめポイント
- JavaScriptのオブジェクトは他のオブジェクトを親にして性質や動作を共有できる。
- この親をプロトタイプと呼び、それをたどる仕組みがプロトタイプチェーン。
newで作ったオブジェクトは作成関数のプロトタイプを親に持つ。- チェーンの最後は
nullで終わる。 - プロトタイプチェーンは継承の基礎であり、効率よく機能を共有できる。
まとめ
ここまでJavaScriptの根幹を支える「プロトタイプチェーン」という仕組みについて詳しく解説してきました。プロトタイプチェーンは、一見すると非常に複雑で難解な概念に思えるかもしれません。しかし、その本質は「オブジェクト間で機能や情報を共有するための、非常に効率的で賢いバケツリレーのような仕組み」であると言えます。
JavaScriptにおけるすべてのオブジェクトは、自身の背後に「親」となるオブジェクト(プロトタイプ)を隠し持っています。私たちが特定のプロパティやメソッドを呼び出そうとしたとき、そのオブジェクト自身がそれを持っていなければ、自動的に親へと探しに行き、さらに親がいなければその上の親へと探索を続けます。この連鎖こそがプロトタイプチェーンの正体です。この仕組みがあるおかげで、私たちは同じような機能を持つオブジェクトを何個も作る際に、同じコードを何度も書く必要がなくなり、コンピュータのメモリを節約しながら柔軟なプログラミングを行うことができるのです。
プロトタイプチェーンの重要性と継承のメリット
プロトタイプチェーンを理解することは、JavaScriptにおける「継承」をマスターすることと同義です。例えば、大規模なアプリケーションを開発する際、共通の機能を持つ「基本クラス」を作り、そこから特定の機能を追加した「派生クラス」を量産する手法がよく取られます。プロトタイプチェーンはこの基盤となっており、コードの再利用性を劇的に高めてくれます。
また、JavaScriptの標準機能である配列(Array)や文字列(String)が、push()やtoUpperCase()といった便利なメソッドを最初から使えるのも、このプロトタイプチェーンのおかげです。これらはすべて、グローバルな Array.prototype や String.prototype から機能を継承しているのです。
実践的なコード例:プロトタイプベースの継承
ここでは、学んだ内容をさらに深めるために、もう少し実用的なコードを見てみましょう。複数のユーザーを管理するようなシステムを想定し、共通の挨拶機能をプロトタイプに持たせる例です。
// ユーザーの雛形となるコンストラクタ関数
function Member(id, name) {
this.id = id;
this.name = name;
}
// すべてのメンバーで共有するメソッドをプロトタイプに追加
Member.prototype.showInfo = function() {
console.log("ID:" + this.id + " 名前:" + this.name);
};
// インスタンスの生成
const user1 = new Member(1, "佐藤");
const user2 = new Member(2, "鈴木");
// メソッドの実行(プロトタイプチェーンを通じて呼び出される)
user1.showInfo();
user2.showInfo();
// user1自身がshowInfoを持っているか確認
console.log(user1.hasOwnProperty('showInfo'));
上記のコードを実行すると、次のような結果が得られます。
ID:1 名前:佐藤
ID:2 名前:鈴木
false
user1.hasOwnProperty('showInfo') が false になる点に注目してください。これは showInfo というメソッドが user1 自身に定義されているのではなく、その親である Member.prototype に存在していることを証明しています。このように、実体としてのデータ(IDや名前)は各オブジェクトが持ち、振る舞い(メソッド)はプロトタイプで共有するというのが、JavaScriptらしい効率的な設計手法です。
TypeScriptにおけるプロトタイプチェーン
最近では型安全な開発のためにTypeScriptを利用することも多いですが、TypeScriptの「クラス(class)」も、コンパイルされてJavaScriptになれば、結局はこのプロトタイプチェーンの仕組みを利用して動いています。TypeScriptでの記述例も見てみましょう。
class Vehicle {
move(): void {
console.log("移動しています...");
}
}
class Car extends Vehicle {
honk(): void {
console.log("プップー!");
}
}
const myCar = new Car();
myCar.move(); // 親クラスのメソッドを呼び出し
myCar.honk(); // 自身のメソッドを呼び出し
この extends というキーワードによる継承も、裏側では Car.prototype のプロトタイプを Vehicle.prototype に設定するという、プロトタイプチェーンの操作が行われています。基礎を理解しておけば、新しい言語仕様やフレームワークが登場しても、その動作原理をすぐに把握できるようになります。
生徒
「先生、プロトタイプチェーンの仕組みがだいぶ見えてきました!要するに、『自分になければ親に聞く、親になければおじいちゃんに聞く』という家系図みたいな探索ルートのことなんですね。」
先生
「その通りです。とても分かりやすい例えですね!JavaScriptのオブジェクトは皆、その家系図のどこかに属しているんです。そして、最終的に辿り着くのが Object.prototype で、その先は null になるんでしたね。」
生徒
「はい。でも、どうしてわざわざそんな回りくどいことをするんですか?最初から全部のオブジェクトにメソッドを持たせちゃダメなんですか?」
先生
「良い質問ですね。例えば、1万個のオブジェクトを作ったとしましょう。もしすべてのオブジェクトに同じメソッドをコピーして持たせたら、メモリを大量に消費してしまいますよね?プロトタイプに一つだけ置いておけば、1万個のオブジェクトがそれを参照するだけで済む。つまり、省エネでスマートなプログラムになるわけです。」
生徒
「なるほど、メモリの節約になるんですね。あと、さっきの __proto__ はあまり使わない方がいいって言ってましたが、現場ではどう書くのが正解なんですか?」
先生
「現代のJavaScriptでは、クラス構文(class)を使うのが一般的です。内部的にはプロトタイプチェーンを使っていますが、人間が読み書きしやすいように工夫されています。ただ、トラブルが起きた時や、高度なライブラリを読み解く時には、今回学んだプロトタイプの知識が必ず役に立ちますよ。」
生徒
「見えないところで支えてくれている大事な仕組みなんですね。プロトタイプチェーンを知っていると、自分が書いているコードの裏側が見える気がして楽しいです!」
先生
「その感覚を大切にしてください。基礎をしっかり固めれば、応用的な非同期処理やクロージャなどもスムーズに理解できるようになります。これからも一緒に頑張りましょう!」