继承
JS
中的继承到底有多少种实现方式呢?答:总共是6种,分别是 原型链继承
、构造函数继承
、组合继承
、原型式继承
、寄生式继承
、寄生组合式继承
。
ES6
中的 extends 关键字是用哪种继承方式实现的呢?答:extends
是通过 寄生组合式继承
实现的,它只是一种 es6
中的语法糖。
1、原型链继承,将父类的实例作为子类的原型
他的特点是实例是子类的实例也是父 类的实例,父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实 现,
缺点:原型对象的所有属性被所有实例共享**;无法实现多继承;无法向父类构造函数传参。
// 定义一个父类
function Animal() {this.like = ['eat', 'drink', 'sleep'];
}
// 定义一个子类
function Cat() {this.name = 'limingcan';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
2、构造继承(构造函数的继承解决了原型链继承中子类实例共享父类原型对象属性的问题),使用父类的构造函数来增强子类实例(实现方式是在子类内部调用父类方法,并通过call改变它的作用域,指向子类的this,从而把父类的方法拿过来),
优点:构造继承可以向父类传递参数,可以实现多继承,
缺点:构造继承只能**继承父类的属性值,不能继承原型属性和方法
// 定义一个父类
function Animal(name) {
this.name = name;
this.play = function() {console.log('到处玩');}
}
// 定义一个子类
function Cat(name, age) {
Animal.call(this, name);
this.age = age;
}
3、组合继承:利用原型链继承跟借用构造函数继承相结合 。
优点: 利用原型链继承,将实例子类、父类三者的原型链串联起来,让实例对象继承父类原型
缺点: 两次调用两次调用父类构造函数,始化两次实例方法/属性
function Animal(name, sex) {
this.name = name;
}
// 定义一个子类
function Cat(name, sex, age) {
// 第一次调用Animal构造函数
Animal.call(this, name, sex);
this.age = age;
}
// 核心:将Cat的原型指向父类Animal的一个实例(第二次调用Animal构造函数)
Cat.prototype = new Animal(); //_proto_指向Animal实例
//组合继承也是需要修复构造函数指向的
Cat.prototype.constructor = Cat;
4、寄生组合继承(是组合继承的优化):
通过寄生方式,砍掉父类的实例属性(通过父类原型和子类原型指向同一对象)
把原型链继承里,Cat.prototype = new Animal()
这一步,用寄生式继承**的思想,用Object.create()
方法实现并替换掉
function Parent () {
this.name = 'zxx'
}
Parent.prototype.test = function () {
console.log('我是函数')
}
function Child () {
Parent.call(this)
this.age = 18
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
/*等价于
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Object.create的底层实现
Object.create = function (o) {
var F = function () {}
F.prototype = o
return new F()
}
优点:减少了构造函数的次数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Xm9Opai-1680244933884)(C:\Users\syhgly\AppData\Roaming\Typora\typora-user-images\1680243298351.png)]
5.原型式继承
用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。一般使用Object.create()
方法实现:第一个参数是用作于新对象原型的对象,第二个参数是可选参数,主要是给新对象定义额外属性的对象
// 定义一个父类(新建出来的对象的__proto__会指向它)
const Animal = {
name: 'nobody'
,run() {console.log('跑步');}
};
// 新建以Animal为原型的实例
const cat = Object.create(Animal,// 这里定义的是实例自身的方法或属性{name: {value: 'limingcan'}}
);
优点:实现比原型链继承更简洁(不需要写什么构造函数了,也不需要写子类,直接父类继承) 子类实例可以直接访问到父类实例或父类原型上的属性方法。
缺点:原型对象的所有属性被所有实例共享(父类所有的引用类型属性都会实例出来的对象共享,所以修改一个实例对象的引用类型属性,会导致所有实例对象受到影响);无法实现多继承;无法向父类构造函数传参
6.寄生式继承
对原型式继承进行一个小封装,增强了一下实例出来的对象
// 定义一个父类(新建出来的对象的__proto__会指向它)
const Animal = {
name: 'nobody',
,run() {console.log('跑步');}
};
// 定义一个封装Object.create()方法的函数
const createObj = (parentPropety, ownProperty) => {
// 生成一个以parentPropety 为原型的对象obj// ownProperty 是新建出来的实例,拥有自身的属性跟方法配置
const obj = Object.create(parentPropety, ownProperty);
// 增强功能
obj.catwalk = function() {console.log('走猫步');}
;return obj;
}
// 新建以Animal为原型的实例一
const cat = createObj(Animal, {name: {value: 'limingcan'}
})
寄生式继承优缺点跟原型式继承一样,但最重要的是它提供了一个类似工厂的思想
7.ES6-Class继承
通过 babel
转换后,最终的实现方法还是基于我们前面的 寄生组合式继承
,因此也证明了 寄生组合式继承
是目前最优的解决方式
三个关键字:① class关键字。②extends关键字。③super关键字
class ZxxFn {
// 类的构造方法
constructor (name, age) {
this.name = name
this.age = age
}
showName () {
console.log('调用父类的构造方法')
console.log(this.name)
}
}
class ZxxSubFn extends ZxxFn {
constructor (name, age, salary) {
super(name, age); // 调用父类的构造方法
this.salary = salary
}
// 父类的方法重写
showName () {
console.log(super.name) // undefined
super是指向父类的prototype对象
// 父类的方法是定义在父类的原型中,而属性是定义在父类对象上的,所以需要把属性定义在原型上
// ZxxFn.prototype.age = age
console.log('调用子类的构造方法')
console.log(this.name, this.salary)
}
}
let zxx1 = new ZxxSubFn('zxx', 18, 88888888)
console.log(zxx1) // ZxxSubFn {name: "zxx", age: 18, salary: 88888888}
zxx1.showName() // zxx
补充:
核心是寄生组合继承方式,不过这里加了一个Object.setPrototypeOf(subClass, superClass),是用来继承父类的静态方法。这也是原来的继承方式疏忽掉的地方。
function _inherits (subClass, superClass) {
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}