前言
在学习基于类面向对象编程语言的时候,我们总会接触到继承的概念,子类能够通过关键字继承父类的公开(public)或保护(protected)属性与方法
子类继承父类后,子类的实例即可访问父类的属性、方法,这对于描述现实世界中事物的通用关系非常有利,比如:兔子、修狗、猫猫各为子类,继承动物父类,父类中定义动物的行为、属性,子类在继承时可以根据实际情况设置/读取父类的属性、调用/重写父类的方法
而在JavaScript中同样有描述现实世界中事物的通用关系的需求,但作为一门"无类"语言,JavaScript通过原型的方式实现继承(原型将在另一篇文章中介绍)
继承分类
原型链继承
原理
将子类构造函数的原型设置为一个新的父类实例,以后所有通过子类构造函数生产的实例公用一份父类实例的属性和方法
const SuperType = function() {
this.colors = ['black', 'white', 'grey'];
}
SuperType.prototype.getColor = function () {
return this.colors;
}
const SubType = function () {
this.sayYes = function () {
console.log('yes');
}
}
SubType.prototype = new SuperType(); // 将SuperType实例作为SubType的原型对象
// SubType.prototype.constructor = SubType; // 修正将原型指向父类实例所产生的副作用
let subTypeInstance1 = new SubType();
subTypeInstance1.colors.push('purple');
console.log(subTypeInstance1.colors); // ['black', 'white', 'grey', 'purple']
let subTypeInstance2 = new SubType();
console.log(subTypeInstance2.colors); // ['black', 'white', 'grey', 'purple']
console.log(subTypeInstance2.getColor());
盗用构造函数继承
原理
在子类构造函数中调用SuperType.call(this),SuperType构造函数内部将在this上挂载相关的属性和方法
优点
SubType的每个实例都会拥有SuperType中属性的副本
缺点
- 由于方法必须定义在构造函数中,每次创建子类必须call一次父类方法
- 由于通过
SuperType.call(this)来继承父类的实例属性和方法(只作用于this)故不能继承父类原型上的属性和方法 - 无法实现复用,每个子类都有父类构造函数的实例副本
const SuperType = function(name) {
this.name = name;
this.getName = function() {
return this.name;
}
}
SuperType.prototype.sayHello = function () {
console.log('hello');
}
const SubType = function (name) {
SuperType.call(this, name);
this.sayYes = function () {
console.log('yes');
}
}
let subType1 = new SubType('xiaobai');
console.log(subType1.getName()); // xiaobai
let subType2 = new SubType('xiaohei');
console.log(subType2.getName()); // xiaohei
subType2.sayYes();
console.log(subType2.sayHello); // undefined
实例继承
原理
在子类构造函数当中创建父类实例并为其添加属性及方法,最后将父类实例作为子类实例返回
优点
- 通过
new SubType()和SubType()调用子类构造函数的返回结果相同
缺点
- 返回的实例是父类的实例,而非子类实例
- 返回的实例将无法使用子类原型的属性、方法
const SuperType = function(name) {
this.name = name;
this.getName = function() {
return this.name;
}
}
SuperType.prototype.sayHello = function () {
console.log('hello');
}
const SubType = function(name = 'xiaobai'){
let instance = new SuperType();
instance.name = name;
return instance;
}
let subType = new SubType();
console.log(subType.name) // xiaobai
subType.getName() // xiaobai
console.log(subType instanceof SuperType); // true
console.log(subType instanceof SubType); // false
组合继承
原理
通过将父类实例作为子类原型,实现函数复用
SuperType.call(this, name);继承父类this上的所有属性并支持传参
优点
- 避免盗用构造函数继承的缺点,子类实例既可访问父类的属性,也可访问父类原型的属性
- 解决引用属性共享一份数据的问题
- 可以正常传参
缺点
- 调用两次父类构造函数造成资源浪费,同时存在两份父类属性数据(一份在子类实例上,一份在原型中)(子类实例将子类原型上的数据屏蔽)
- 第一次调用 |
SubType.prototype = new SuperType();-向SubType.prototype中写入属性name、color - 第二次调用 |
SuperType.call(this, name);-给子类实例this写入两个属性name
- 第一次调用 |
const SuperType = function (name) {
this.name = name;
this.colors = ['white', 'black', 'brown'];
}
SuperType.prototype.getName = function () {
return this.name;
}
const SubType = function (name) {
SuperType.call(this, name); // 第二次调用父类构造函数
}
SubType.prototype = new SuperType(); // 第一次调用父类构造函数
SubType.prototype.constructor = SubType; // 修正将原型指向父类实例所产生的副作用
let subType1 = new SubType('lucy');
subType1.colors.push('purple');
console.log(subType1.colors); // [ 'white', 'black', 'brown', 'purple' ]
console.log(subType1.getName()); // lucy
let subType2 = new SubType('Macy');
console.log(subType2.colors); // [ 'white', 'black', 'brown' ]
console.log(subType2.getName()); // Macy
原型式继承
原理 创建一个空对象并将需要继承的对象直接赋值给空对象的原型
缺点
- 多个实例的引用类型属性指向相同,存在恶意修改风险
- 无法传递参数
const SubType = {
name: 'Atom',
colors: ['black', 'white', 'grey']
};
let subTypeInstance1 = Object.create(SubType);
subTypeInstance1.colors.push('purple');
let subTypeInstance2 = Object.create(SubType);
console.log(subTypeInstance2.colors); // [ 'black', 'white', 'grey', 'purple' ]
可见通过原型继承产生的对象共享同一份SubType的数据
寄生式继承
原理
在原型式继承的基础上进行优化(为构造函数新增属性、方法)以增强对象
缺点
- 多个实例的引用类型属性指向相同,存在恶意修改风险
- 无法传递参数
const SubType = {
name: 'Atom',
colors: ['black', 'white', 'grey']
};
const createObject = function (origin) {
const obj = Object.create(origin);
obj.getColors = function () {
return this.colors;
}
return obj;
}
let subTypeInstance1 = createObject(SubType);
subTypeInstance1.colors.push('purple');
let subTypeInstance2 = createObject(SubType);
console.log(subTypeInstance2.colors);
console.log(subTypeInstance2.getColors()); // [ 'black', 'white', 'grey', 'purple' ]
寄生组合式继承
原理
- 盗用构造函数传递参数
- 寄生式继承实现对父类的继承
优点
- 当前最常用的继承实现
- 只调用了一次
SuperType构造函数,避免了在SubType的原型上创建多余的属性 - 支持使用
instanceof和isPrototypeOf()
缺点
- 基本上无
const inheritPrototype = function (son, father) {
let proto = Object.create(father.prototype);
proto.constructor = son;
son.prototype = proto;
}
const SuperType = function (name) {
this.name = name;
this.colors = ['white', 'black', 'brown'];
}
SuperType.prototype.getName = function () {
return this.name;
}
const SubType = function (name) {
SuperType.call(this, name);
}
inheritPrototype(SubType, SuperType); // 寄生组合式继承关键
let subType1 = new SubType('lucy');
subType1.colors.push('purple');
console.log(subType1.colors); // [ 'white', 'black', 'brown', 'purple' ]
console.log(subType1.getName()); // lucy
let subType2 = new SubType('Macy');
console.log(subType2.colors); // [ 'white', 'black', 'brown' ]
console.log(subType2.getName()); // Macy
结语
继承在实际工作当中是非常重要的基础知识,身为一名前端开发者必须要拿下这块知识
OK,以上就是全部内容啦,如果你感觉这篇文章写得还不错的话,求点赞!求评论!最后再点一个大大的关注,你的支持是我坚持产出的最大动力!
陈同学还在学习的路上不断前行~!