以下、継承を実装していくにあたり、この記事では、次の2つのコンストラクタを用意する。
まず、Personコンストラクタ。プロパティは、nameとageを持つ。そしてプロトタイプに、sayName()とsayMyself()という、2つのメソッドを持つ。
そして、Personコンストラクタを継承する、Studentコンストラクタ。上記の他に、gradeプロパティを持つ。また、このコンストラクタのインスタンスは、sayMyself()を実行した際、学生であることと、学年を表示する。
これを実現するための方法を、いくつか見ていく。
プロトタイプに、継承元となるコンストラクタのインスタンスを割り当てる
// まず、元となるPersonコンストラクタを定義 function Person(name, age){ this.name = name; this.age = age; }; Person.prototype.sayName = function(){ console.log('私の名前は' + this.name + 'です。'); }; Person.prototype.sayMyself = function(){ console.log('私は人間です。'); }; // 次に、Studentコンストラクタを定義 function Student(name, age, grade){ this.name = name; this.age = age; this.grade = grade; }; // Studentのプロトタイプに、Personのインスタンスを設定 Student.prototype = new Person(); Student.prototype.constructor = Student; // sayMyselfはStudent独自の内容にする Student.prototype.sayMyself = function(){ console.log('私は学生です。学年は' + this.grade + 'です。'); }; var person1 = new Person('Tom', 30); var person2 = new Student('Ichiro', 20, 2); person1.sayName(); // 私の名前はTomです。 person1.sayMyself(); // 私は人間です。 person2.sayName(); // 私の名前はIchiroです。 person2.sayMyself(); // 私は学生です。学年は2です
上記のコードでは、person2がsayMyself()を実行しようとしても、person2自身はそのメソッドを持っていない。
そのため、自身のプロトタイプオブジェクトであるStudent.prototypeから探してきて、それを実行する。
sayName()を実行しようとした際も、同じプロセスが行われる。だが今度は、プロトタイプオブジェクトにも、このメソッドはない。
そこでさらに、プロトタイプのプロトタイプ、つまりPerson.prototypeからsayName()を探してきて、実行する。
// person2は、sayMyselfを持っていない console.log(person2.hasOwnProperty('sayMyself')); // false // person2のプロトタイプは、Student.prototype console.log(Student.prototype.isPrototypeOf(person2)); // true // そしてStudent.prototypeは、sayMyselfを持っている console.log(Student.prototype.hasOwnProperty('sayMyself')); // true // person2は、sayNameを持っていない console.log(person2.hasOwnProperty('sayName')); // false // Student.prototypeも、持っていない console.log(Student.prototype.hasOwnProperty('sayName')); // false // Student.prototypeのプロトタイプは、Person.prototype console.log(Person.prototype.isPrototypeOf(Student.prototype)); // true // Person.prototypeは、sayNameを持っている console.log(Person.prototype.hasOwnProperty('sayName')); // true
Object.create()を使う
Object.create()を使うことでも、同様のことが出来る。
Object.create()の基本的な使い方は、こちらを参照。
Object.creat()とプロトタイプチェーン
// まず、元となるPersonコンストラクタを定義 function Person(name, age){ this.name = name; this.age = age; }; Person.prototype.sayName = function(){ console.log('私の名前は' + this.name + 'です。'); }; Person.prototype.sayMyself = function(){ console.log('私は人間です。'); }; // 次に、Studentコンストラクタを定義 function Student(name, age, grade){ this.name = name; this.age = age; this.grade = grade; }; // Studentのプロトタイプに、Person.prototypeを設定 Student.prototype = Object.create(Person.prototype, { constructor:{ configurable: true, enumerable: true, writable: true, value: Student } }); // sayMyselfはStudent独自の内容にする Student.prototype.sayMyself = function(){ console.log('私は学生です。学年は' + this.grade + 'です。'); }; var person1 = new Person('Tom', 30); var person2 = new Student('Ichiro', 20, 2); person1.sayName(); // 私の名前はTomです。 person1.sayMyself(); // 私は人間です。 person2.sayName(); // 私の名前はIchiroです。 person2.sayMyself(); // 私は学生です。学年は2です
結果も、そこに至るプロトタイプチェーンのメカニズムも、先程と全く同じである。
メソッド以外のプロパティの継承について
ここまで、継承の方法を見てきたが、いずれも継承しているのはメソッドのみである。
メソッド以外のプロパティについては、継承しておらず、それぞれのインスタンスで個別に定義していた。
この方法だとムダが多いし、記述を間違うリスクも高まる。
call()やapply()を使うことで、メソッド以外のプロパティについても、継承を行えるようになる。
そうすることで、同じプロパティ(この場合はnameとage)をわざわざ個別に設定していくことを回避できる。
call()やapply()については、下記を参照。
this, call(), apply(), bind()
// まず、元となるPersonコンストラクタを定義 function Person(name, age){ this.name = name; this.age = age; }; Person.prototype.sayName = function(){ console.log('私の名前は' + this.name + 'です。'); }; Person.prototype.sayMyself = function(){ console.log('私は人間です。'); }; // 次に、Studentコンストラクタを定義 function Student(name, age, grade){ Person.call(this, name, age); this.grade = grade; }; // Studentのプロトタイプに、Person.prototypeを設定 Student.prototype = Object.create(Person.prototype, { constructor:{ configurable: true, enumerable: true, writable: true, value: Student } }); // sayMyselfはStudent独自の内容にする Student.prototype.sayMyself = function(){ console.log('私は学生です。学年は' + this.grade + 'です。'); }; var person1 = new Person('Tom', 30); var person2 = new Student('Ichiro', 20, 2); person1.sayName(); // 私の名前はTomです。 person1.sayMyself(); // 私は人間です。 person2.sayName(); // 私の名前はIchiroです。 person2.sayMyself(); // 私は学生です。学年は2です
上記の例では、Studentのインスタンスを生成する際、生成されるインスタンスをthisとして、Personが呼び出される。
そのため、Personの
this.name = name; this.age = age;
が実行され、インスタンスにnameプロパティとageプロパティが設定される。
オーバーライドされたメソッドを使う
ここまで何度も見てきたように、今回の例では、Student.prototype.sayMyselfを定義することで、Person.prototype.sayMyselfをオーバーライドしている。
そのため、Studentのインスタンスからは、Person.prototype.sayMyselfにアクセスできない。
だが、オーバーライドされたメソッドにアクセスしたいケースもあるかもしれない。
その際にも、call()やapply()を使うことで、解決できる。
// まず、元となるPersonコンストラクタを定義 function Person(name, age){ this.name = name; this.age = age; }; Person.prototype.sayName = function(){ console.log('私の名前は' + this.name + 'です。'); }; Person.prototype.sayMyself = function(){ console.log('私は人間です。'); }; // 次に、Studentコンストラクタを定義 function Student(name, age, grade){ Person.call(this, name, age); this.grade = grade; }; // Studentのプロトタイプに、Person.prototypeを設定 Student.prototype = Object.create(Person.prototype, { constructor:{ configurable: true, enumerable: true, writable: true, value: Student } }); Student.prototype.sayMyself = function(){ Person.prototype.sayMyself.call(this); console.log('そして'); console.log('私は学生です。学年は' + this.grade + 'です。'); }; var person1 = new Student('Ichiro', 20, 2); person1.sayMyself(); // 私は人間です。 // そして // 私は学生です。学年は2です。
Studentのインスタンスであるperson1から、Person.prototype.sayMyselfにアクセスしていることが分かる。