这可能是你一直在寻找的JS八大继承方法
前言
JS主流的继承主要有这八种:原型链继承、(借用)构造函数继承、组合式继承、原型继承、寄生式继承、寄生组合式继承、混入继承、ES6
类继承。其中最重要的是寄生组合式继承,是目前最为广泛使用,且较为完美的继承方案,希望你能够对其认真学习。
在正式介绍这八种继承方法之前,首先需要知道能够被继承的属性方法主要分成两大部分,一部分是在原型对象上的,一部分是在实例化对象上的。比如:
function Parent(name) {
this.name = name // 实例化对象上
}
// 原型对象上的
Parent.prototype.show = function() {
console.log(this.name)
}
接下来我们会围绕这两个位置的属性方法来对各个类型的继承进行讨论。以此来清晰的展示各个方法之间的优缺点。
原型链继承
// 父类拥有两个位置的属性方法
function Parent() {
this.hobby = ['study', 'run']
}
Parent.prototype.show = function() {
console.log('my hobby is: ' + this.hobby.join('、'))
}
function Child() {}
Child.prototype = new Parent() // !核心实现
const child1 = new Child()
const child2 = new Child()
child1.show() // my hobby is: study、run
child2.show() // my hobby is: study、run
- 原理:修改子类的原型对象为父类的实例化对象
- 优点:父类实例上和原型对象上的属性方法都能被子类继承
- 缺陷:当对父类实例上的属性(引用数据类型)进行修改时,会影响到其他实例化的对象
child1.hobby.push('basketball')
child1.show() // my hobby is: study、run、basketball
child2.show() // my hobby is: study、run、basketball
因为Child.prototype = new Parent()
时,子类的原型对象是父类的一个实例化对象,当子类的实例化对象修改hobby
时,其实是修改new Parent()
创建的对象,所有就会影响到其他实例化对象了
(借用)构造函数继承
function Parent() {
this.hobby = ['study', 'run']
}
Parent.prototype.say = function() {
console.log('say hello ~ ~')
}
function Child() {
Parent.call(this) // !核心实现
this.show = function() {
console.log(this.hobby)
}
}
const child1 = new Child()
const child2 = new Child()
child1.show() // my hobby is: study、run
child2.show() // my hobby is: study、run
- 原理:在子类内部调用父类函数,不是实例化对象,只是当作普通函数调用(通过显式绑定来修改this指向)
- 优点:解决了原型链继承的问题,因为此时
hobby
属性是存在于子类实例化对象上的
child1.hobby.push('basketball')
child1.show() // my hobby is: study、run、basketball
child2.show() // my hobby is: study、run
- 缺陷:父类原型对象上的属性方法不能被继承
try {
child1.say()
} catch (error) {
console.log('err')
}
结合继承
function Parent(name) {
this.name = name
this.hobby = ['study', 'run']
}
Parent.prototype.show = function() {
console.log('my hobby is: ' + this.hobby.join('、'))
}
function Child(name, age) {
this.age = age
Parent.call(this, name) // !核心实现
this.say = function() {
console.log('my name is: ' + this.name)
}
}
Child.prototype = new Parent() // !核心实现
const child1 = new Child('child1',18)
const child2 = new Child('child2',28)
child1.say() // my name is: child1
child2.say() // my name is: child2
- 原理:对原型链继承和构造函数继承进行了结合,因此叫结合式继承
- 优点:解决了原型链继承和构造函数继承的问题,可以完全继承父类两个部分的属性方法
// 解决了构造函数继承的问题,可以使用父类原型对象上的属性方法了
child1.show() // my hobby is: study、run
child2.show() // my hobby is: study、run
// 解决了原型链继承的问题
child1.hobby.push('basketball')
child1.show() // my hobby is: study、run、basketball
child2.show() // my hobby is: study、run (child2的hobby不受影响)
- 缺陷:父类实例化对象上的属性同时存在于子类原型对象和子类的实例化对象上
原型继承
// 效果与Object.create相同,就是将新创建的对象的__proto__指向obj
// let a = Object.create(b)
// a.__proto__ = b // true
function objectCopy(obj) {
function F() {}
F.prototype = obj // !核心实现
return new F()
}
const child = {
name: 'person',
hobby: ['study', 'run'],
sayName() {
console.log(this.name)
},
show() {
console.log('my hobby is: ' + this.hobby.join('、'))
}
}
const child1 = objectCopy(child) // 与Object.create(child)效果相同
const child2 = objectCopy(child)
child1.name = 'child1'
child2.name = 'child2'
child1.sayName() // child1
child2.sayName() // child2
- 原理:在调用
objectCopy
函数时,会返回F
的实例化对象,F
的原型对象是obj
,实际原理跟原型链继承相同。区别在于父类直接是一个对象,也可以理解成是对该对象的浅拷贝。 - 缺陷:和原型链继承一样,当对父类实例上的属性(引用数据类型)进行修改时,会影响到其他实例化的对象
child1.hobby.push('basketball')
child1.show() // my hobby is: study、run、basketball
child2.show() // my hobby is: study、run、basketball
寄生继承
function objectCopy(obj) {
function F() {}
F.prototype = obj
return new F()
}
// 核心实现
function createAnother(original) {
const clone = objectCopy(original)
// 拓展能力
// 位于实例化对象上
clone.sayName = function() {
console.log(this.name)
}
return clone
}
const child = {
name: 'person',
hobby: ['study', 'run'],
show() {
console.log(this.hobby.join('、'))
}
}
const child1 = createAnother(child)
const child2 = createAnother(child)
child1.name = 'child1'
child2.name = 'child2'
child1.sayName() // child1
child2.sayName() // child2
- 原理:主要是为了增强原型继承的能力,使用了一个函数,在其内部进行原型继承以及能力拓展。其实跟
child1.sayName = function() {}
相同 - 优点:增强了能力,拓展的函数方法存在于实例化对象上
- 缺陷:和原型继承相同
child1.hobby.push('basketball')
child1.show() // my hobby is: study、run、basketball
child2.show() // my hobby is: study、run、basketball
寄生组合式继承
function Parent(name) {
this.name = name
this.hobby = ['study', 'run']
}
Parent.prototype.sayName = function() {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name) // !核心实现,构造函数式继承
this.age = age
}
// !核心实现
// 构建原型链
// 执行完该函数后,child.__proto__ === parent.prototype
function inheritPrototype(child, parent) {
let prototype = Object.create(parent.prototype) // 与F函数效果相同
prototype.constructor = child // 修改原型对象的构造函数指向,原本为parent
child.prototype = prototype // 将父类的原型对象赋给子类的原型对象
}
inheritPrototype(Child, Parent)
Child.prototype.sayAge = function() {
console.log(this.age)
}
Child.prototype.show = function() {
console.log('my hobby is: ' + this.hobby.join('、'))
}
const child1 = new Child('child1', 18)
const child2 = new Child('child2', 88)
console.log(child1.age) // 子类实例化对象上的属性
child1.sayAge() // 子类原型对象上的方法
console.log(child1.name) // 父类实例化上的属性,通过构造函数继承式得到的
child1.sayName() // 父类原型对象上的方法,通过寄生式继承得到的
child1.hobby.push('basketball')
child1.show() // my hobby is: study、run、basketball
child2.show() // my hobby is: study、run(不被影响)
-
原理:我们知道组合式继承其实是原型链继承和构造函数继承,寄生式继承又是原型继承的增强版,而原型继承跟原型链继承差不多。因此寄生组合式继承就是将这些方式给混合了。
比如继承父类实例上的属性方法就是通过构造函数继承;父类原型对象上的属性方法则是通过寄生式继承
inheritPrototype
函数。 -
优点:与组合式继承相同,都能继承父类原型对象上以及实例化上的属性方法,而且解决了组合式继承中父类实例化属性方法同时存在子类实例化对象和子类原型对象上的问题。
是目前最完美的解决方案,许多类库的继承都是选用这个方法
混入继承
function Parent1() {
this.hobby = ['study', 'run']
}
Parent1.prototype.show = function() {
console.log(this.hobby.join('、'))
}
function Parent2(name) {
this.name = name
}
Parent2.prototype.sayName = function() {
console.log(this.name)
}
function Child(name, age) {
this.age = age
Parent1.call(this) // 构造函数模式,继承父类的实例化属性
Parent2.call(this, name)
}
function inheritPrototype(child, parent1, parent2) {
let prototype = Object.create(parent1.prototype)
Object.assign(prototype, parent2.prototype) // !核心实现,混入
prototype.constructor = child
child.prototype = prototype
}
inheritPrototype(Child, Parent1, Parent2)
Child.prototype.sayAge = function() {
console.log(this.age)
}
const child1 = new Child('child1', 18)
console.log(child1.age) // 子类实例化对象上的属性
child1.sayAge() // 子类原型对象上的方法
console.log(child1.hobby, child1.name) // 实例对象上的属性,分别继承于父类实例对象
child1.show() // 父类1原型对象上的方法
child1.sayName() // 子类原型对象上的方法,通过混入父类2原型对象获取到的
-
原理:在寄生组合式继承的基础上拓展,实现了继承多父类。核心在于
Object.assign(prototype, parent2.prototype)
,通过该函数,将父类2的原型对象与子类的原型对象合并,因此需要注意的是:父类1的原型对象和父类2的原型对象不在同一级不过也可以对
inheritPrototype
进行修改,使其在同一级function inheritPrototype(child, parent1, parent2) { let parent_prototype = Object.assign(parent1.prototype, parent2.prototype) let prototype = Object.create(parent_prototype) prototype.constructor = child child.prototype = prototype }
类继承
class Parent {
constructor(name, age) {
this.name = name
this.age = age
}
sayName() {
console.log(this.name)
}
sayAge() {
console.log(this.age)
}
}
class Child extends Parent {
constructor(name, age, hobby) {
super(name, age)
this.hobby = hobby
}
show() {
console.log(this.hobby.join('、'))
}
}
const child1 = new Child('child1', 18, ['study', 'run'])
child1.sayAge() // 18
child1.sayName() // child1
child1.show() // study、run
- 原理:没啥好说的,
class
语法根本上是语法糖,extends
的底层实现与寄生组合式继承类似