プロトタイプでオブジェクトの継承を実装する
この記事では「プロトタイプとプロトタイプチェーン」で説明した内容を一歩進めて、オブジェクトの継承関係についてもう少し詳しく見ていきたいと思います。まだ読んでない方は 「プロトタイプとプロトタイプチェーン」を先に見ていただいた方がわかりやすいと思います。
オブジェクトの継承関係の実装方法を、具体例で説明します。
継承元のコンストラクタを呼び出す
Personコンストラクタと、fooメソッドの定義は次の通りとします。
// Person コンストラクタ
function Person(name, age) {
this.name = name;
this.age = age;
}
// コンストラクタの prototype に foo 関数を作成
Person.prototype.foo = function() {
console.log(`foo: ${this.name}(${this.age})`);
}
この時、Personコンストラクタのprototypeプロパティとプロトタイプオブジェクトは、次の図のように繋がっています。
この Person を継承して、Employeeオブジェクトを作りましょう。
”Person" は「人」のことです。"Employee" は「従業員」のことです。「従業員は人」ですから、いわゆる継承の "is-a" の関係ですね。
Employeeコンストラクタには、title (役職) という名前の引数を一つ増やしましょう。 このときコンストラクタを、どのように書けば良いでしょうか。
今回は Personコンストラクタが、thisにname と ageを保存しているだけである、ということを知っているので、 次のようにしても問題はありません。
function Employee(name, age, title) {
this.name = name;
this.age = age;
this.title = title;
}
しかし、一般的には継承元のコンストラクタで何をしているか必ずしも知っているわけではありません。 ましてや、受け取ったものをただ保存するだけであるとも限りません。
このため、オブジェクトを正しく初期化するには、コンストラクタの中で、継承元であるPersonコンストラクタを呼び出します。
function Employee(name, age, title) {
Person.call(this, name, age);
this.title = title;
}
このとき、コンストラクタは new を付けないで呼び出されることになります。 関連情報は「コンストラクタ」を見てください。
プロトタイプの構成
Employeeオブジェクトには、Personオブジェクトが持つメソッドに加えてさらに、新たにbarメソッドを追加しましょう。
まずは Person オブジェクトの性質を獲得するために、Person コンストラクタの prototype プロパティを元にしてプロトタイプオブジェクトを生成して、Employee の prototype プロパティに設定します。
Employee.prototype = Object.create(Person.prototype);
これで Employeeオブジェクトのプロトタイプチェーンに、Personのプロトタイプチェーンが繋がります。
ちなみに、このとき Employee.prototypeが指しているプロトタイプオブジェクトの内容は、コンストラクタへの参照も含めてクリアされます。 これを直すために再度、コンストラクタをセットし直します。
Employee.prototype.constructor = Employee;
「コンストラクタを設定し直すのは必要か?」で、この意味について説明しています。
これでメソッドを追加する準備ができました。
Employeeに barメソッドを追加します。
Employee.prototype.bar = function() {
console.log(`bar: ${this.name}(${this.age}) ${this.title}`);
};
これにより、Employee.prototypeが指しているプロトタイプオブジェクトに、barメソッドが追加されます。
以上で、Personクラスを継承して、Employeeクラスを定義することができました。
クドクドと説明を混えていたので、長ったらしいと感じたかもしれませんが、まとめて書くと次のようになります。
//コンストラクタ
function Employee(name, age, title) {
Person.call(this, name, age);
this.title = title;
}
//プロトタイプ
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
//メソッド
Employee.prototype.bar = function() {
console.log(`bar: ${this.name}(${this.age}) ${this.title}`);
};
決まり切ったやり方なので、一度ステップを覚えてしまえば簡単です。
オブジェクトを作成して、メソッドを呼ぶ
それではEmployeeオブジェクトを作って、動作確認してみましょう。
Employeeのプロトタイプに作成したbarメソッドの他、 Personのプロトタイプに作成されているfooメソッドも呼び出しましょう。
// Person
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.foo = function() {
console.log(`foo: ${this.name}(${this.age})`);
};
// Employee
function Employee(name, age, title) {
Person.call(this, name, age);
this.title = title;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.bar = function() {
console.log(`bar: ${this.name}(${this.age}) ${this.title}`);
};
// 動作確認
// Employee オブジェクトの作成
let e = new Employee('Ichiro', 30, 'Manager');
// オブジェクトをコンソールに出力
console.log(e);
// メソッドを呼ぶ
e.bar();
e.foo();
// instanceof でチェック
console.log('Is e an instance of Person? ' + (e instanceof Person));
console.log('Is e an instance of Employee? ' + (e instanceof Employee));
実行結果は次の通りです。
確かに期待通り、barメソッド、fooメソッド共に呼ぶことができました。
これはプロトタイプによって、継承関係が正しく認識されているからです。
さらに instanceof オペレータを使って、Personオブジェクトであり、 かつ Employeeオブジェクトであることも確認できました。
以上で、プロトタイプを用いた継承関係の実装方法について説明しました。