JS常见继承方式

98 阅读4分钟

所谓继承可以理解为利用原型让一个引用类型继承另一个引用类型的属性和方法。

我列举了一下继承之后所期望的状态:

    1. 可以继承父类构造函数属性
    1. 子类可以向父类传参
    1. 可以继承父类原型属性
    1. 可以识别是父级的实例
    1. 引用类型的属性不会被其它实例共享

以下总结常见的几种种继承方式:

原型链继承

function Person(name) {
  this.name = name
  this.orgName = 'person'
  this.arrs = []
}
Person.prototype.getName = function() {
  return this.orgName
}

function Child() {}
// 直接给原型赋值
Child.prototype = new Person()

// 无法进行传参
const child1 = new Child("child name")

const child2 = new Child()
// 对实例继承的引用类型属性进行修改,会被child1共享
child2.arrs.push("child2")

console.log(child1, child2, child1 instanceof Person, child1.getName())

缺点:

    1. 子类实例化时无法向父类传参

child1.name = undefined

    1. 引用类型的属性会被其它实例共享

child1.arrs = ["child2"] / child2.arrs = ["child2"]

结论:

    1. 可继承父类构造函数属性
    1. 子类无法向父类传参
    1. 可继承父类原型属性
    1. 可识别是父级的实例
    1. 引用类型的属性会被其它实例共享

构造函数继承

function Person(name) {
  this.name = name
  this.orgName = 'person'
  this.arrs = []
}
Person.prototype.getName = function() {
  return this.name
}

function Child(name) {
  Person.call(this, name)
}

const child1 = new Child("child")
const child2 = new Child()
child2.arrs.push("child2")

console.log(child1, child2, child1 instanceof Person)

缺点:

    1. 无法识别是父级的实例

child1 instanceof Person => false

    1. 无法继承父类原型上的属性

child1.getName() => error: child1.getName is not a function

    1. 子类方法都在构造函数中定义,无法复用构造函数,每次创建实例都会创建一遍方法

结论:

    1. 可继承父类构造函数属性
    1. 子类可以向父类传参
    1. 可以继承父类原型属性
    1. 可以识别为是父级的实例
    1. 引用类型的属性不会被其它实例共享

组合继承

组合继承:结合原型链继承/构造函数继承,使用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承,解决了原型链继承不可向父级传参和构造函数无法继承父类原型属性的问题等,但是缺点是会调用2次父类构造函数

function Person(name) {
  this.name = name
  this.orgName = 'person'
  this.arrs = []
}
Person.prototype.getName = function() {
  return this.name
}

function Child(name) {
  Person.call(this, name)
}
Child.prototype = new Person()

const child1 = new Child("child")
const child2 = new Child()
child2.arrs.push("child2")

console.log(child1, child2, child1 instanceof Person, child1.getName())

缺点:

    1. 调用2次父类构造函数

结论:

    1. 可继承父类构造函数属性
    1. 子类可以向父类传参
    1. 可以继承父类原型属性
    1. 可以识别为是父级的实例
    1. 引用类型的属性不会被其它实例共享

原型式继承

function Person(name) {
  this.name = name
  this.orgName = 'person'
  this.arrs = []
}
Person.prototype.getName = function() {
  return this.name
}
const person = new Person()

function Child(person) {
  const F = function() {}
  F.prototype = person
  return new F()
}

const child1 = new Child(person)
const child2 = new Child(person)
child2.arrs.push("child2")

console.log(child1, child2, child1 instanceof Person, child1.getName())

缺点:

    1. 所有实例都会继承原型上的属性
    1. 无法实现复用

结论:

    1. 可继承父类构造函数属性
    1. 子类可以向父类传参
    1. 可以继承父类原型属性
    1. 可以识别为是父级的实例
    1. 引用类型的属性会被其它实例共享

寄生式继承

寄生继承就是给原型式继承外面封装一层function


function Person(name) {
  this.name = name
  this.orgName = 'person'
  this.arrs = []
}
Person.prototype.getName = function() {
  return this.name
}

// 原型式继承
function CreateInstance(person) {
  const F = function() {}
  F.prototype = person
  return new F()
}

// 以上是原型式继承,再给原型式继承函数包装一层进行参数传递
function Child(obj) {
  const sub = CreateInstance(obj)
  return sub
}

// 需要将父类进行实例化后传入
const person1 = new Person("person1")
const person2 = new Person("person2")

const child1 = new Child(person1)
const child2 = new Child(person2)

child2.arrs.push("child2")

console.log(child1, child2, child1 instanceof Person, child1.getName())

缺点:

    1. 无法实现复用

结论:

    1. 可继承父类构造函数属性
    1. 子类可以向父类传参
    1. 可以继承父类原型属性
    1. 可以识别为是父级的实例
    1. 引用类型的属性不会被其它实例共享

寄生组合式继承

修复了组合继承的问题 寄生:在函数内返回对象然后调用 组合:

    1. 函数的原型等于另一个实例
    1. 在函数中用apply或者call引入另一个构造函数,可传参

function Person(name) {
  this.name = name
  this.orgName = 'person'
  this.arrs = []
}
Person.prototype.getName = function() {
  return this.name
}

function CreateInstance(person) {
  const F = function() {}
  F.prototype = person
  return new F()
}

const instance = CreateInstance(Person.prototype)

function Child(name) {
  Person.call(this, name)
}

Child.prototype = instance
instance.constructor = Child

const child1 = new Child('child1')
const child2 = new Child()

child2.arrs.push("child2")

console.log(child1, child2, child1 instanceof Person, child1.getName())

缺点:

    1. 无法实现复用

结论:

    1. 可继承父类构造函数属性
    1. 子类可以向父类传参
    1. 可以继承父类原型属性
    1. 可以识别为是父级的实例
    1. 引用类型的属性不会被其它实例共享