JavaScriptはオブジェクト指向のプログラミング言語であり、その中核にはプロトタイプベースの継承という特有の仕組みがあります。ここでは、JavaScriptのプロトタイプチェーンの詳細を理解し、クラス構文の内部で何が起きているのか、またオブジェクト指向プログラミング(OOP)をどのようにJavaScriptで実装するかを詳しく見ていきます。
プロトタイプチェーンとは
JavaScriptでは、すべてのオブジェクトは他のオブジェクトからプロパティやメソッドを継承することができます。これをプロトタイプチェーンと呼びます。
- プロトタイプオブジェクト: JavaScriptのオブジェクトは、内部的に
[[Prototype]]
という隠しプロパティを持ちます。このプロパティは他のオブジェクトへの参照であり、オブジェクトが持っていないプロパティやメソッドを参照するために使われます。 - プロトタイプチェーンの動作: プロパティやメソッドをオブジェクトに対して呼び出すと、JavaScriptエンジンはまずそのオブジェクト自身をチェックし、存在しない場合は
[[Prototype]]
を辿って親オブジェクトを探します。このチェーンがどんどん続いていき、最終的にnull
に達するまで探索が続きます。これがプロトタイプチェーンです。
例として、以下のコードを考えてみましょう:
javascript
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
const alice = new Person('Alice');
alice.greet(); // "Hello, my name is Alice"
ここで、alice
オブジェクトにはgreet
メソッドがありません。しかし、Person.prototype
に定義されたgreet
メソッドをプロトタイプチェーンを通じて利用しています。
クラス構文と内部の挙動
ES6で導入されたクラス構文は、JavaScriptでオブジェクト指向プログラミングをより扱いやすくするための糖衣構文です。クラスは、見た目が他の言語(JavaやC++など)のクラスと似ていますが、実際にはプロトタイプベースの仕組みの上に構築されています。
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const dog = new Dog('Rex');
dog.speak(); // "Rex barks."
- クラスとプロトタイプ: 上記のコードで
class Animal
を定義していますが、内部的にはコンストラクタ関数として動作しています。speak
メソッドはAnimal.prototype
に追加されており、継承するDog
クラスはAnimal
を拡張することでプロトタイプチェーンを利用しています。 - 継承の仕組み:
Dog
クラスはAnimal
クラスをextends
しており、super
キーワードを使うことで親クラスのメソッドやプロパティにアクセスできます。このように、JavaScriptのクラスはプロトタイプチェーンをベースにして継承を実現しています。
オブジェクト指向プログラミングのパターン
JavaScriptでは、さまざまなオブジェクト指向プログラミングのパターンを実装できます。以下はいくつかの代表的なパターンです。
コンストラクタパターン: コンストラクタ関数を用いてオブジェクトを生成し、そのプロトタイプにメソッドを追加する方法です。プロトタイプチェーンを使って効率的にメモリを節約し、共通のメソッドをすべてのインスタンスで共有します。
javascriptfunction Car(make, model) { this.make = make; this.model = model; } Car.prototype.start = function() { console.log(`${this.make} ${this.model} is starting.`); }; const myCar = new Car('Toyota', 'Corolla'); myCar.start();
モジュールパターン: JavaScriptでは、モジュールパターンを使ってデータの隠蔽と名前空間を管理できます。IIFE(即時実行関数)を使って、プライベートなスコープを持つオブジェクトを作成することが一般的です。
javascriptconst Calculator = (function() { let result = 0; return { add: function(x) { result += x; }, getResult: function() { return result; } }; })(); Calculator.add(5); console.log(Calculator.getResult()); // 5
シングルトンパターン: シングルトンは、特定のクラスが一つのインスタンスしか持たないことを保証するパターンです。例えば、アプリケーション全体で一つだけ存在する設定管理オブジェクトなどに使われます。
javascriptconst Singleton = (function() { let instance; function createInstance() { return { name: 'I am the instance' }; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; })(); const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance(); console.log(instance1 === instance2); // true
まとめ
JavaScriptのプロトタイプベースの継承は、クラスベースの他の言語とは異なる独特な仕組みです。class
構文は、より直感的にオブジェクト指向プログラミングを行うための表現方法ですが、内部ではプロトタイプチェーンが使われています。JavaScriptのプロトタイプと継承の理解を深めることで、オブジェクト指向プログラミングのさまざまなパターンを効果的に利用し、柔軟で再利用可能なコードを書くことが可能です。