Chapter 06

プロトタイプパターン

Shinya Fujino
Shinya Fujino
2022.01.07に更新

複数の同じ型のオブジェクトの間でプロパティを共有する


プロトタイプパターン

プロトタイプパターン (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");

ここで、constructorname プロパティがあり、クラス自体に 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 プロパティを追加することで可能となります。

index.js
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 という名前の空飛ぶ犬を作って、吠えたり飛んだりさせてあげましょう!

index.js
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 プロパティにアクセスできるのです。

index.js
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 は、新しく作成されるオブジェクトのプロトタイプを指定することで、あるオブジェクトに他のオブジェクトのプロパティを直接継承させる簡易な方法です。新しいオブジェクトは、プロトタイプチェーンをたどり、新しいプロパティにアクセスすることができます。


プロトタイプパターンは、あるオブジェクトが他のオブジェクトのプロパティにアクセスしたり、それを継承したりすることを容易に可能とします。プロトタイプチェーンにより、オブジェクトは自身に直接定義されていないプロパティにアクセスできるため、メソッドやプロパティの重複を避け、使用するメモリ容量を削減することができます。


参考文献