プロトタイプ継承はJavaScriptのオブジェクト指向プログラミング手法です
オブジェクト指向プログラミングは、現代のソフトウェア開発における重要な概念の一つです。コードの再利用性、保守性、拡張性を高める強力な手法を提供するため、多くの開発者が学び、活用しています。
JavaScriptは、その動的で柔軟な特性により、多くの人気を集めています。しかし、JavaScriptでオブジェクト指向プログラミングを正しく理解し活用するには、プロトタイプ継承の理解が不可欠です。この記事では、JavaScriptのプロトタイプ継承について深く理解できるように、解説と具体的な例を交えて紹介します。プロトタイプ継承を通じて、オブジェクト指向プログラミングをより簡単かつ効率的に活用する方法を見ていきましょう。
オブジェクト指向プログラミング(Object-oriented programming)とは
オブジェクト指向プログラミングは、ソフトウェア開発の手法の一つです。 もともとオブジェクト指向プログラミングは存在していた方法ではなく、ソフトウェア開発で起きた問題を解決するために生まれました。 初めは、プログラミングは主に手続き的プログラミング(Procedural Programming)という順序に従って処理を進める方法で行われていました。しかし、ソフトウェアの規模が大きくなるにつれて、コードの再利用性や保守性、拡張性に対する要求が高まっていきました。手続き的プログラミングはコードのモジュール化が難しく、一度書いたコードを再利用するのも困難でした。さらに、機能を変更・追加する際には既存のコードを修正しなければならないことが多く、保守も困難でした。これらの課題を解決するために、オブジェクト指向プログラミングが登場したのです。
オブジェクト指向プログラミングを理解する前に、まず手続き的プログラミング(「手続き指向プログラミング」または「手続き指向的プログラミング」とも呼ばれます)について見ていきましょう。
手続き的プログラミング
手続き的プログラミングは、プログラムを開発者が論理的に組み立てた順序、つまり一連の手続きに従って構成する方法を指します。そのため、多くの場合、処理するデータを中心に、そのデータを操作する関数を基にコードを書きます。簡単な例を使って、手続き的プログラミングについて見ていきましょう。
以下の例は、自動車に関連する手続き的プログラミングのコードです。
let carName = "ポルシェ";
let carModel = "911 タルガ";
let carColor = "white";
const startCar = name => {
console.log(`${name} 出発`);
}
const helloCar = (name, model, color) => {
const hello = `私の車の名前は${name}で、モデル名は${model}です。色は${color}です。`;
console.log(hello);
}
const drivingCar = name => {
console.log(`${name} 運転!`);
}
// 使用例
startCar(carName); // 出力: "ポルシェ 出発"
helloCar(carName, carModel, carColor); // 出力: "私の車の名前はポルシェで、モデル名は911 タルガです。色はwhiteです。"
drivingCar(carName); // 出力: "ポルシェ 運転!"
上記のJavaScriptコード例は、手続き的プログラミング方式で書かれた簡単な自動車関連の関数群です。各関数は特定の処理を行い、それらを呼び出すことで目的の結果を得る仕組みになっています。
オブジェクト指向プログラミング方式
前述のように、手続き的プログラミングはコードのモジュール化が難しく、一度書いたコードを再利用するのも困難という欠点がありました。また、機能を変更・追加する際に既存のコードを修正しなければならない場合が多く、保守も困難でした。これらの問題を解決するために、オブジェクト指向プログラミングが登場しました。
コードの再利用性、保守性、拡張性は非常に重要な課題となり、これらを向上させるために現実世界の物に関する概念を取り入れ、コードの再利用性、保守性、拡張性を高めるという考えが生まれました。
例えば、自動車は現実世界の物です。自動車はエンジンやタイヤ、座席など複数の構成要素を持っています。また、自動車は走行、停止、旋回などさまざまな機能も備えています。オブジェクト指向プログラミングでは、自動車をオブジェクトとして表現します。自動車オブジェクトはエンジンやタイヤ、座席といった属性(プロパティ)を持ち、さらに走行、停止、旋回といったメソッド(オブジェクトに含まれる関数)も持っています。
以下は、先ほどの例をJavaScriptのプロトタイプ継承を使ったオブジェクト指向プログラミング方式のコードに書き換えた例です。
// Carオブジェクトのコンストラクタ関数
function Car(name, model, color) {
this.name = name;
this.model = model;
this.color = color;
}
// Carオブジェクトのメソッドをプロトタイプに定義
Car.prototype.start = function() {
console.log(`${this.name} 出発`);
};
Car.prototype.hello = function() {
const hello = `私の車の名前は${this.name}で、モデル名は${this.model}です。色は${this.color}です。`;
console.log(hello);
};
Car.prototype.drive = function() {
console.log(`${this.name} 運転!`);
};
// 使用例
let myCar = new Car("ポルシェ", "911 タルガ", "white");
myCar.start(); // 出力: "ポルシェ 出発"
myCar.hello(); // 出力: "私の車の名前はポルシェで、モデル名は911 タルガです。色はwhiteです。"
myCar.drive(); // 出力: "ポルシェ 運転!"
上記の例を見ると、手続き的プログラミング方式とは大きな違いがあります。
手続き的プログラミング方式では複数の個別の関数を使用していました。一方、オブジェクト指向プログラミング方式も複数の関数を使っているように見えますが、使用されているすべての関数は myCar.start()
、myCar.hello()
、myCar.drive()
のように myCar.xxxx()
の形になっており、「myCarのstart」「myCarのhello」「myCarのdrive」のように「myCar」というオブジェクトとそのオブジェクトに属する関数として認識しやすくなっています。
オブジェクトとは、現実世界の物や概念をプログラム上で客観的に表現するための入れ物であり、データとそのデータを処理する関数をひとまとめにしたものです。例えば、自動車オブジェクトは自動車の属性(色、モデルなど)と動作(加速、停止など)を持っています。このようにオブジェクトはデータとそれを操作する関数を一緒に持つことで、管理や利用が容易になります。つまり、データとそれを処理するメソッドを含むプログラミングの要素です。
このようにオブジェクト指向プログラミング方式は、コードのインターフェースを把握しやすいという利点があります。また、プログラミングの拡張性、カプセル化、パフォーマンスの向上、保守の利便性向上など、多くのメリットがあります。では、JavaScriptのオブジェクト指向プログラミング方式の核心であるプロトタイプ継承について見ていきましょう。
프로토타입 상속이란? 프로토타입이라는 방식으로 부모(혹은 조상) 객체가 가지고 있는 속성이나 메서드를 자식(혹은) 객체에 상속되는 것을 말합니다. 프로토타입 상속이 무엇인지 다음의 예제로 살펴보겠습니다.
JavaScriptのプロトタイプ継承
// 親オブジェクトの宣言
function Animal() {
this.name = "Animal"; // 親オブジェクト(Animal)が持っている属性です。
}
// 親オブジェクト(Animal)にプロトタイプという方法でメソッドを追加
Animal.prototype.say = function() {
console.log("I am an animal.");
};
// 子オブジェクトの宣言
function Dog() {
this.name = "Dog";
}
// プロトタイプという方法で親オブジェクト(Animal)が持っている属性を子オブジェクト(Dog)に継承させる
Dog.prototype = new Animal();
// 子(Dog)オブジェクトで親(Animal)の属性とメソッドを使います。
const myDog = new Dog();
console.log(myDog.name); // 出力: "Dog"
myDog.say(); // 出力: "I am an animal."
上記のコード例は、プロトタイプ継承をわかりやすく説明するための例です。
- まず、
Animal
という親オブジェクトを宣言します。このオブジェクトにはname
という属性が定義されています。 Animal.prototype
を使って親オブジェクトにメソッドsay()
を追加します。このメソッドは「I am an animal.」というメッセージを出力します。- 次に、
Dog
という子オブジェクトを宣言します。このオブジェクトは親オブジェクトであるAnimal
を継承する必要があります。 Dog.prototype
を親オブジェクトAnimal
のインスタンスに設定することで、親オブジェクトの属性とメソッドを子オブジェクトに継承させます。myDog
というオブジェクトを生成します。このオブジェクトはDog
のインスタンスです。myDog.name
を出力すると「Dog」が表示されます。これは子オブジェクトであるDog
がname
属性を持っているためです。- 最後に、
myDog.say()
を呼び出すと「I am an animal.」というメッセージが表示されます。これは親オブジェクトであるAnimal
のメソッドsay()
を継承して使用しているためです
このように、プロトタイプを利用して親オブジェクトの属性やメソッドを子オブジェクトに継承し、使用することができます。
プロトタイプ(Prototype)方式とは何か
プロトタイプ(Prototype)とは何でしょうか。
辞書的な意味では「元の形または原本」を指します。JavaScriptでは「プロトタイプはオブジェクトを作成する際に使用するテンプレートまたは原本」という意味で使われます。簡単に言うと、オブジェクトの原本と考えることができます。
例えば、自動車の設計図と考えることができます。自動車の設計図には、すべての自動車が持つべき共通の特徴や機能が定義されています。この設計図をもとにして複数の自動車を製造することができます。このとき、それぞれの自動車は設計図を基に作られるのです。
同様に、プロトタイプはオブジェクトを作成する際に使われるテンプレートや原本と考えることができます。プロトタイプには、そのオブジェクトが持つべき共通の属性やメソッドが定義されています。JavaScriptでは、すべてのオブジェクトが自身のプロトタイプを持っており、このプロトタイプを通じて共通の属性やメソッドを継承することができます。
例えば、複数の自動車オブジェクトを生成する際に、一つのプロトタイプを作成し、それを基にそれぞれの自動車オブジェクトを生成することができます。こうすることで、それぞれの自動車オブジェクトはプロトタイプに定義された属性やメソッドを共有することになります。
プロトタイプを使うことで、コードの再利用性と効率性を向上させることができます。オブジェクトを生成するたびに同じ属性やメソッドを定義する必要がなく、プロトタイプから継承して利用できるためです。
このようなプロトタイプ方式では、プロトタイプを実装するためのprototype
プロパティと、いくつかの追加のプロパティが提供されます。
prototype
プロパティ
「プロトタイプ」とも呼ばれます。すべてのオブジェクトは prototype
プロパティを持ち、これを通じてプロパティやメソッドを継承できます。
prototype
プロパティの使い方
JavaScriptでは、オブジェクトはprototype
プロパティを持っています。このprototype
プロパティは該当オブジェクトのプロトタイプオブジェクトを指します。プロトタイプオブジェクトに定義された属性やメソッドは、そのオブジェクトを継承した他のオブジェクトで共有されます。
例えば、Animal
というコンストラクタ関数があり、この関数から生成されたオブジェクトdog
があるとします。Animal.prototype
は、Animal
コンストラクタ関数から生成されたすべてのオブジェクトが継承するプロトタイプオブジェクトです。
function Animal(name) {
this.name = name;
}
// Animalコンストラクタ関数から生成されたすべてのオブジェクトが継承するプロトタイプオブジェクト
Animal.prototype.speak = function() {
console.log(`こんにちは、${this.name}です。`);
};
const dog = new Animal("ワンちゃん");
dog.speak(); // 出力: "こんにちは、ワンちゃんです。"
上記の例では、Animal.prototype.speak
はAnimal
コンストラクタ関数から生成されたすべてのオブジェクトが継承するメソッドです。dog
オブジェクトはAnimal.prototype
を通じてspeak()
メソッドにアクセスできます。
したがって、オブジェクト.prototype
という形式を使ってプロトタイプオブジェクトにアクセスし、属性やメソッドを定義したり取得したりすることができます。
これまでに大まかにprototype
属性について説明しました。JavaScriptでプロトタイプ方式を理解するためには、prototype
属性の役割を正確に理解することが重要です。
オブジェクトリテラルとObject.create()
これまで見てきたプロトタイプ方式の継承は、コンストラクタ関数を使って定義したオブジェクトに対して適用されるものでした。しかし、プロトタイプベースの継承は、コンストラクタ関数で定義されたオブジェクト以外にも利用可能です。JavaScriptはプロトタイプベースの言語であるため、オブジェクト間で継承を実現するためのさまざまな方法が用意されています。
Object.create()
プロパティ(正しくは「メソッド」ですが)を使用すると、既存のオブジェクトをプロトタイプとして設定した新しいオブジェクトを生成できます。これにより、オブジェクト間の継承を簡単に実現することができます。
ここでは、オブジェクトリテラルとObject.create()
プロパティ(※メソッド)を活用した継承の方法について紹介します。
const parent = {
sayHello: function() {
console.log("こんにちは!");
}
};
const child = Object.create(parent);
child.sayHello(); // 出力: "こんにちは!"
上記の例では、child
オブジェクトはparent
オブジェクトをプロトタイプとして継承しており、sayHello()
メソッドを呼び出すことができます。
クラスとextends
ECMAScript 2015(ES6)からは、class
構文とextends
キーワードが導入されました。
クラスを使用することでオブジェクトを定義でき、extends
を使って他のクラスを継承することが可能です。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}が鳴きます。`);
}
}
class Dog extends Animal { // extendsキーワードでAnimalクラスを継承
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(`${this.name}が吠えます。`);
}
}
const myDog = new Dog("ワンワン", "リトリバー");
myDog.speak(); // 出力: "ワンワンが鳴きます。"
myDog.bark(); // 出力: "ワンワンが吠えます。"
上記の例では、Dog
クラスがAnimal
クラスを継承して使用しています。
まとめ
今回の記事では、JavaScriptのプロトタイプ継承について解説しました。プロトタイプ継承はJavaScriptの重要な概念の一つであり、オブジェクト指向プログラミングにおける強力なツールです。
私たちはプロトタイプとは何か、プロトタイプチェーンがどのように動作するか、コンストラクタ関数とプロトタイプ方式を利用した継承方法について学びました。また、別の継承方法としてオブジェクトリテラルとObject.create()
、クラスとextends
キーワードを使用した継承についても簡単に確認しました。
プロトタイプ継承はオブジェクト指向プログラミングを理解し活用するうえで重要な概念です。これによりコードの再利用性を高め、保守性を改善できるほか、オブジェクト間の関係を効率的に構築することが可能になります。JavaScriptのプロトタイプ継承を理解することで、より効果的なコード作成とJavaScriptの核心的な概念を深く理解できます。プロトタイプ継承の理解を基盤に、JavaScript開発に対する自信を持ち、より効率的で拡張性の高いコードを書くことができるようになるでしょう。