1. 原型链继承
原型链继承的原理很简单,直接让子类的原型对象指向父类的实例,当子类的实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承
function Person() {
this.name = 'Back_kk';
}
Person.prototype.getName = function() {
return this.name;
}
function Student() {}
// 让子类的原型对象指向父类的实例
Student.prototype = new Person();
// 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要
// 原型的实例等于自身
Student.prototype.constructor = Student;
const student = new Student();
console.log(student.name); // Back_kk
console.log(student.getName()); // Back_kk
缺点:
1. 由于所有Student实例原型都指向同一个Person实例, 因此对某个Student实例的来自父类的引用类型变量修改会影响所有的Student实例
2. 在创建子类实例时无法向父类构造传参, 即没有实现super()的功能
function Person() {
this.obj = {
name: 'Back_kk',
age: 18
};
}
function Student() {}
Student.prototype = new Person();
// 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要
// 原型的实例等于自身
Student.prototype.constructor = Student;
const student1 = new Student();
student1.obj.name = '佩奇';
const student2 = new Student();
console.log(student2.obj.name); // 佩奇
2. 借用构造函数继承
构造函数继承,即在子类的构造函数中调用父类的构造函数,使用apply() 或 call() 方法将父对象的构造函数绑定在子对象上,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参。
function Person(name) {
this.name = name
}
Person.prototype.getName = function() {
return this.name;
}
function Student() {
Person.apply(this, arguments);
}
const student = new Student('Back_kk');
console.log(student.name); // Back_kk
console.log(student.getName()); // 报错 student.getName is not a function
优点:
- 解决了原型链实现继承的不能传参的问题,和父类的原型共享的问题。
缺点:
- 父类的方法如果定在在原型上,则对子类而言是不可见的,即子类继承不到父类原型上的属性和方法
- 为了子类能继承父类的方法,则需要把方法定义在构造函数中,这样会无法实现函数的复用,占用内存
3. 组合式继承
组合继承结合了原型集成和构造函数继承的特点。
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
function Student() {
// 构造函数继承
Person.apply(this, arguments)
}
// 原型式继承
Student.prototype = new Person();
// 原型的实例等于自身
Student.prototype.constructor = Student;
const student = new Student('Back_kk');
console.log(student.name); // Back_kk
console.log(student.getName()); // Back_kk
缺点:
每次创建子类实例都执行了两次构造函数 (Person.apply和new Person()),虽然这并不影响对父类的继承,但子类创建实例时,原型链上会存在两份相同的属性和方法,这并不优雅。
“原型链上会存在两份相同的属性和方法”,其实是Person.prototype.proto === Person,导致原型链多出了一层,所以会存在两份属性和方法。
我们从上图中可以看出,Person.prototype的原型对象的构造函数指向Person自身。
4. ES6的class类继承
ES6 实现了Class,可以通过关键字extends实现继承
其实质是先创造出父类的this对象,然后用子类的构造函数修改this
注意:子类的构造方法中必须调用super方法,且只有在调用了super()之后才能使用this,因为子类的this对象是继承父类的this对象,然后对其进行加工,而super方法表示的是父类的构造函数,用来新建父类的this对象
// 父类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
getName() {
console.log(this.name);
}
}
// 子类extends继承父类,必须调用super方法
class Student extends Person {
constructor(name, age, address) {
super(name, age);
this.address = address;
}
say() {
console.log(`hello,我叫${this.name},我的住址在${this.address}`);
}
}
const stu1 = new Student('张三', 20, '西安市');
// 调用父类的方法
stu1.getName(); // 张三
// 调用自己的方法
stu1.say(); // hello,我叫张三,我的住址在西安市
缺点:
ES6提出的,有的浏览器不支持该写法