如果我们学习过面向对象编程语言,比如说Java,那么我们对继承这个概念不会陌生。在类似于Java的语言中,继承基本都是使用extends关键字来实现,而在js中却不是这样(本篇文章只考虑es6之前继承的实现方式,es6中已经可以使用extends关键字)
什么是继承?
继承的定义:通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承 。比如说:A对象通过继承B对象,就能直接拥有B对象的属性和方法,从而可以调用B对象的属性和方法。
js中实现继承的方法
js中有很多种方法可以实现继承,这里我们只讨论三种方法:原型链继承、构造继承、组合继承。
我们先定义一个父类构造函数:
function Person(name, age) {
//定义父类的属性和方法
this.name = name;
this.age = age;
this.greed = function() {
console.log('Hello, I am ' + this.name);
}
}
//定义一个父类原型上的方法
Person.prototype.getInfo = function() {
console.log(this.name + ',' + this.age);
}
原型链继承
原型链继承就是修改原型链实现继承,通过将子类的prototype指向父类的实例来实现。
//定义子类的构造函数
function Student1() {
}
Student1.prototype = new Person();//子类的prototype指向父类的实例
//修改从父类继承过来的属性
Student1.prototype.name = 'xiaoming';
Student1.prototype.age = 22;
let student1 = new Student1();
//调用父类方法
student1.getInfo();//xiaoming,22
在原型链继承这种方式中,student1对象既是子类的实例,又是父类的实例。缺点是在子类的构造函数中无法通过传参的方式对父类继承的属性值进行修改,只能通过修改prototype的方式进行修改。
构造继承
构造继承就是在子类的构造函数中调用父类的构造函数实现继承
//定义子类的构造函数
function Student2(name, age, sex) {
Person.call(this);//调用父类的构造函数
//修改父类的属性
this.name = name;
this.age = age;
//子类自身的属性
this.sex = sex;
}
let student2 = new Student2('xiaoming', 22, 'male');
student2.greed();//Hello,I am xiaoming
student2.getInfo()//报错:student2.getInfo is not a function
构造继承避免了原型链继承的缺点,直接在子类中调用父类的构造函数,这样,student2只是子类的实例。然而这种方式的缺点是子类实例只能调用父类实例中定义的方法,不能调用父类原型上定义的方法。
组合继承
组合继承是原型链继承和构造继承的组合体
//定义子类的构造函数
function Student3(name, age, sex) {
Person.call(this);//(第一次)调用父类的构造函数
//修改父类的属性
this.name = name;
this.age = age;
//子类自身的属性
this.sex = sex;
}
Student3.prototype = new Person();//(第二次)调用父类的构造函数
let student3 = new Student3('xiaoming',22,'male');
student3.greed();//Hello,I am xiaoming
student3.getInfo();//xiaoming,22
组合继承结合了原型链继承和构造继承方式的优点,唯一的问题是需要调用父类的构造函数两次,这造成了一定的内存浪费。