继承的方式

原型链继承

子类的原型指向父类的一个实例。

function Parent(age) {
this.name = 'parent'
this.isShow = true
this.colors = ['red', 'pink', 'blue']
this.age = age
}

Parent.prototype.getInfo = function () {
console.log(this.name, this.isShow, this.colors, this.age)
}

function Child(age) { }

// 核心代码,创建 Parent 的实例,并将该实例赋值给S Child.prototype
Child.prototype = new Parent()

const children1 = new Child(18)
children1.name = 'newName'
children1.isShow = false
children1.colors.push('black')
children1.getInfo() // newName false [ 'red', 'pink', 'blue', 'black' ] undefined

const children2 = new Child(20)
children2.getInfo() // parent true [ 'red', 'pink', 'blue', 'black' ] undefined

优点:父类方法可以复用

缺点:

  1. 父类中的引用类型属性会被篡改。
  2. 子类实例不能给父类构造函数传参。

借用构造函数继承

使用 call()apply() 方法,在子类构造函数中调用父类构造函数。

function Parent(age) {
this.name = 'parent'
this.isShow = true
this.colors = ['red', 'pink', 'blue']
this.age = age
this.say = () => {
console.log(this.name + ' 说了你好!')
}
}

function Child(age) {
// 核心代码
Parent.call(this, age)
}

const children1 = new Child(18)
children1.name = 'newName'
children1.isShow = false
children1.colors.push('black')
children1.say() // newName 说了你好!
console.log(children1.name, children1.isShow, children1.age, children1.colors) // newName false 18 [ 'red', 'pink', 'blue', 'black' ]

const children2 = new Child(20)
children1.say() // newName 说了你好!
console.log(children2.name, children2.isShow, children2.age, children2.colors) // parent true 20 [ 'red', 'pink', 'blue' ]

优点:

  1. 父类中的引用类型属性不会被篡改。
  2. 子类实例可以给父类构造函数传参。

缺点:

  1. 不能复用,每个子类都有父类实例的副本,影响性能。
  2. 只能继承父类实例的 属性和方法,不能继承原型上的属性和方法,因此所有方法都写在构造函数中,每次创建实例都会初始化。

组合继承

组合继承综合了 原型链继承构造函数继承,将两者的优点结合了起来。

基本的思路:使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性,这样既可以把方法定义在原型上以实现复用,还能让属性不被共享。

function Parent(age) {
this.name = 'parent'
this.isShow = true
this.colors = ['red', 'pink', 'blue']
this.age = age
this.say = () => {
console.log(this.name + ' 说了你好!')
}
}

Parent.prototype.getInfo = function () {
console.log(this.name, this.isShow, this.age, this.colors)
}

function Child(age) {
// 核心代码,继承父类属性
Parent.call(this, age)
}
// 核心代码,继承父类的方法
Child.prototype = new Parent()
// 核心代码,重写 Child.prototype 的 constructor 属性,指向自己的构造函数 Child
Child.prototype.constructor = Child

const children1 = new Child(18)
children1.name = 'newName'
children1.isShow = false
children1.colors.push('black')
children1.getInfo() // newName false 18 [ 'red', 'pink', 'blue', 'black' ]

const children2 = new Child(20)
children2.getInfo() // parent true 20 [ 'red', 'pink', 'blue' ]

优点:

  1. 父类方法可以复用
  2. 父类中的引用类型属性不会被篡改。
  3. 子类实例可以给父类构造函数传参。

原型式继承

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

function object(obj) {
function F() { }
F.prototype = obj
return new F()
}

object()对传入其中的对象执行了一次 浅复制,将构造函数 F 的原型直接指向传入的对象。

function object(obj) {
function F() { }
F.prototype = obj
return new F()
}

const obj = {
name: '张三',
colors: ['red', 'pink', 'blue'],
say() {
console.log(this.name + ' 说了你好!' + '颜色有:' + this.colors)
}
}

const obj1 = object(obj)
obj1.name = 'newName'
obj1.colors.push('black')
obj1.say() // newName 说了你好!颜色有:red,pink,blue,black

const obj2 = object(obj)
obj2.say() // 张三 说了你好!颜色有:red,pink,blue,black

优点:父类方法可以复用

缺点:

  1. 父类中的引用类型属性会被篡改。
  2. 子类实例不能给父类构造函数传参。

ES5 中存在 Object.create() 的方法,能够代替上面的object方法。

const obj = {
name: '张三',
colors: ['red', 'pink', 'blue'],
say() {
console.log(this.name + ' 说了你好!' + '颜色有:' + this.colors)
}
}

const obj1 = Object.create(obj)
obj1.name = 'newName'
obj1.colors.push('black')
obj1.say() // newName 说了你好!颜色有:red,pink,blue,black

const obj2 = Object.create(obj)
obj2.say() // 张三 说了你好!颜色有:red,pink,blue,black

寄生式继承

使用原型式继承对一个目标对象进行浅复制,增强这个浅复制的能力,返回构造函数。

function object(obj) {
function F() { }
F.prototype = obj
return new F()
}

function createAnother(original) {
const clone = object(original) // 通过调用 object() 函数创建一个新对象
clone.getName = function () { // 以某种方式来增强对象
console.log(this.name)
}
return clone // 返回这个对象
}

const obj = {
name: '张三',
colors: ['red', 'pink', 'blue'],
say() {
console.log(this.name + ' 说了你好!' + '颜色有:' + this.colors)
}
}

const obj1 = createAnother(obj)
obj1.name = 'newName'
obj1.colors.push('black')
obj1.say() // newName 说了你好!颜色有:red,pink,blue,black
obj1.getName() // newName

const obj2 = createAnother(obj)
obj2.say() // 张三 说了你好!颜色有:red,pink,blue,black
obj2.getName() // 张三

优点:父类方法可以复用

缺点:

  1. 父类中的引用类型属性会被篡改。
  2. 子类实例不能给父类构造函数传参。

寄生组合式继承

组合继承综合了 构造函数传递参数寄生模式 实现继承。

寄生式组合继承可以算是引用类型继承的最佳模式,是最成熟的方法,也是现在库实现的方法。

function object(obj) {
function F() { }
F.prototype = obj
return new F()
}

function inheritPrototype(child, parent) {
const prototype = object(parent.prototype)
prototype.constructor = child
Child.prototype = prototype
}

function Parent(age) {
this.isShow = true
this.name = 'parent'
this.colors = ['red', 'pink', 'blue']
this.age = age
this.say = () => {
console.log(this.name + ' 说了你好!')
}
}

Parent.prototype.getInfo = function () {
console.log(this.name, this.isShow, this.colors, this.age)
}

function Child(age) {
// 核心代码
Parent.call(this, age)
}

inheritPrototype(Child, Parent)

const child1 = new Child(18)
child1.name = 'newName'
child1.colors.push('black')
child1.say() // newName 说了你好!
child1.getInfo() // newName true [ 'red', 'pink', 'blue', 'black' ] 18


const child2 = new Child(20)
child2.say() // parent 说了你好
child2.getInfo() // parent true [ 'red', 'pink', 'blue' ] 20

ES6类继承extends

class Parent {
constructor(age) {
this.name = '张三'
this.age = age
this.colors = ['red', 'pink', 'blue']
}

getInfo() {
console.log(this.name, this.age, this.colors)
}
}

class Child extends Parent {
constructor(age) {
super(age)
}
}
const child = new Child(18)
child.getInfo() // 张三 18 [ 'red', 'pink', 'blue' ]