複数の同じ型のオブジェクトの間でプロパティを共有する
プロトタイプパターン
プロトタイプパターン (prototype pattern) は、複数の同じ型のオブジェクトの間でプロパティを共有するために便利な方法です。プロトタイプ (prototype) は JavaScript のネイティブオブジェクトであり、プロトタイプチェーンを通じてオブジェクトからアクセスすることができます。
アプリケーションの中で、同じ型のオブジェクトをたくさん作らなければならないことがよくあります。このような場合に便利なのが、ES6 クラスのインスタンスを複数作成する方法です。
たとえば、犬をたくさん作りたいとしましょう!以下の例では、犬はそれほど多くのことができるわけではありません。名前をもち、吠えることができるだけです。
class Dog {
constructor(name) {
this.name = name;
}
bark() {
return `Woof!`;
}
}
const dog1 = new Dog("Daisy");
const dog2 = new Dog("Max");
const dog3 = new Dog("Spot");
ここで、constructor
に name
プロパティがあり、クラス自体に bark
プロパティがあることに注目してください。ES6 クラスを使用する場合、クラス自体に定義されたすべてのプロパティ (この場合は bark
) は、自動的に prototype
に追加されます。
prototype
は、コンストラクタの prototype
プロパティ、またはインスタンスの __proto__
プロパティにアクセスすることで直接確認することができます。
console.log(Dog.prototype);
// constructor: ƒ Dog(name, breed) bark: ƒ bark()
console.log(dog1.__proto__);
// constructor: ƒ Dog(name, breed) bark: ƒ bark()
コンストラクタのインスタンスの __proto__
の値は、そのコンストラクタのプロトタイプへの直接の参照となります。オブジェクトに直接存在しないプロパティにアクセスしようとすると、JavaScript は、そのプロパティがプロトタイプチェーンの中で利用可能かどうかを確認するために、プロトタイプチェーンを下っていきます。
同じプロパティにアクセスできるはずのオブジェクトを扱うときに、プロトタイプパターンはとても強力です。すべてのインスタンスはプロトタイプオブジェクトにアクセスできるため、プロパティの複製を毎回作成する代わりに、プロパティをプロトタイプに追加するだけでよいのです。
すべてのインスタンスはプロトタイプにアクセスできるため、インスタンスの作成後であっても、プロトタイプにプロパティを追加することが簡単にできます。
たとえば私たちの犬を、吠える以外に遊べるようにもしたいとしましょう。これは、プロトタイプに play
プロパティを追加することで可能となります。
class Dog {
constructor(name) {
this.name = name;
}
bark() {
return `Woof!`;
}
}
const dog1 = new Dog("Daisy");
const dog2 = new Dog("Max");
const dog3 = new Dog("Spot");
Dog.prototype.play = () => console.log("Playing now!");
dog1.play();
プロトタイプチェーンという言葉は、一つ以上の段階があることを示しています。そうなんです!ここまでは、__proto__
が参照をもつ最初のオブジェクトから直接利用可能なプロパティにアクセスする方法について見てきました。ところが、プロトタイプ自身も __proto__
オブジェクトをもっているのです!
別のタイプの犬、スーパードッグを作りましょう。この犬は普通の Dog
からすべてを継承しますが、さらに空を飛ぶことができます。スーパードッグは、Dog
クラスを継承して fly
メソッドを追加することにより作ることができます。
class SuperDog extends Dog {
constructor(name) {
super(name);
}
fly() {
return "Flying!";
}
}
Daisy
という名前の空飛ぶ犬を作って、吠えたり飛んだりさせてあげましょう!
class Dog {
constructor(name) {
this.name = name;
}
bark() {
console.log("Woof!");
}
}
class SuperDog extends Dog {
constructor(name) {
super(name);
}
fly() {
console.log(`Flying!`);
}
}
const dog1 = new SuperDog("Daisy");
dog1.bark();
dog1.fly();
Dog
クラスを継承したので、bark
メソッドにアクセスすることができます。SuperDog
のプロトタイプの __proto__
の値は、Dog.prototype
オブジェクトを指しているのです。
プロトタイプチェーンと呼ばれる理由がこれでわかりました。オブジェクトから直接利用できないプロパティにアクセスしようとすると、JavaScript は、__proto__
が指すすべてのオブジェクトを、そのプロパティが見つかるまで再帰的にたどって回るのです!
Object.create
Object.create
メソッドにより、新しいオブジェクトを作成し、そのオブジェクトに明示的にプロトタイプの値を渡すことができます。
const dog = {
bark() {
return `Woof!`;
}
};
const pet1 = Object.create(dog);
pet1
自身は何のプロパティももっていませんが、プロトタイプチェーンのプロパティにアクセスすることができます!pet1
のプロトタイプとして dog
オブジェクトを渡したので、bark
プロパティにアクセスできるのです。
const dog = {
bark() {
console.log(`Woof!`);
}
};
const pet1 = Object.create(dog);
pet1.bark(); // Woof!
console.log("Direct properties on pet1: ", Object.keys(pet1));
console.log("Properties on pet1's prototype: ", Object.keys(pet1.__proto__));
完璧です!Object.create
は、新しく作成されるオブジェクトのプロトタイプを指定することで、あるオブジェクトに他のオブジェクトのプロパティを直接継承させる簡易な方法です。新しいオブジェクトは、プロトタイプチェーンをたどり、新しいプロパティにアクセスすることができます。
プロトタイプパターンは、あるオブジェクトが他のオブジェクトのプロパティにアクセスしたり、それを継承したりすることを容易に可能とします。プロトタイプチェーンにより、オブジェクトは自身に直接定義されていないプロパティにアクセスできるため、メソッドやプロパティの重複を避け、使用するメモリ容量を削減することができます。