30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

JavaScriptにおける、継承の方法

以下、継承を実装していくにあたり、この記事では、次の2つのコンストラクタを用意する。

まず、Personコンストラクタ。プロパティは、nameageを持つ。そしてプロトタイプに、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です

上記のコードでは、person2sayMyself()を実行しようとしても、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()を使うことで、メソッド以外のプロパティについても、継承を行えるようになる。
そうすることで、同じプロパティ(この場合はnameage)をわざわざ個別に設定していくことを回避できる。

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にアクセスしていることが分かる。