以下、継承を実装していくにあたり、この記事では、次の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
にアクセスしていることが分かる。