JavaScript 继承

原型链继承

  • 优点

简单,易于实现
  • 缺点

  • 无法实现多继承
  • 子类实例无法为父类构造函数传递自己特有的属性值,因为父类构造函数只在为子类构造函数的原型对象赋值时调用了一次,所以每个实例都共享同一样的父类属性值
  • 代码

// 父类构造函数
function Parent (name, age) {
  this.name = name
  this.age = age
  this.parentLog = function () {
    return`${this.name} => ${this.age}`
  }
}

// 子类构造函数
function Child (msg) {
  this.msg = msg
  this.childLog = function () {
    return this.msg
  }
}

// 原型链继承,将父类实例对象作为子类构造函数的原型对象
Child.prototype = new Parent('老爹', 58)

// 子类实例一
const ins1 = new Child('child ins1')
console.log(`子类: ${ins1.childLog()} => 父类: ${ins1.parentLog()}`)  // 子类: child ins1 => 父类: 老爹 => 58

// 子类实例二
const ins2 = new Child('child ins2')
console.log(`子类: ${ins2.childLog()} => 父类: ${ins2.parentLog()}`)  // 子类: child ins2 => 父类: 老爹 => 58

构造函数继承

  • 优点

  • 解决了原型链继承的两个缺点

    • 可实现多继承
    • 在构造子类实例对象时为父类构造函数传递子类实例特有的属性值
  • 缺点

子类无法继承父类构造函数原型对象上的属性和方法
  • 代码

// 父类构造函数
function Parent (name, age) {
  this.name = name
  this.age = age
  this.parentLog = function () {
    return`${this.name} => ${this.age}`
  }
}

// 父类构造函数的原型对象
Parent.prototype.pProperty = 'parent prototype property'
Parent.prototype.pFunction = function () {
  return 'parent prototype function'
}

function Parent_1 (msg) {
  this.p_1_msg = msg
  this.parent_1_log = function () {
    return `${this.msg} => ${this.p_1_msg}`
  }
}

// 子类构造函数
function Child (msg, name, age) {
  // 在子类构造函数中通过call方法在this对象上调用父类构造函数,并且可以调用多个父类构造函数,从而实现多继承
  Parent.call(this, name, age)
  Parent_1.call(this, 'I am parent_1\'s msg')
  this.msg = msg
  this.childLog = function () {
    return this.msg
  }
}

// 子类实例一
const ins1 = new Child('child ins1', '大爹', 68)
console.log(`子类: ${ins1.childLog()} => 父类: ${ins1.parentLog()} => 父类_1: ${ins1.parent_1_log()}`)  // 子类: child ins1 => 父类: 大爹 => 68 => 父类_1: child ins1 => I am parent_1's msg

// 子类实例二
const ins2 = new Child('child ins2', '二爹', 58)
console.log(`子类: ${ins2.childLog()} => 父类: ${ins2.parentLog()} => 父类_1: ${ins2.parent_1_log()}`)  // 子类: child ins2 => 父类: 二爹 => 58 => 父类_1: child ins2 => I am parent_1's msg

// 子类无法继承父类构造函数原型对象上的属性和方法
console.log(`父类原型对象上的属性: ${ins1.pProperty}`)  // 父类原型对象上的属性: undefined
console.log(`父类原型对象上的方法: ${ins1.pFunction()}`)  // Uncaught TypeError

组合继承

  • 优点

更进一步的(更优秀的)一种继承方案,解决了构造函数继承的缺点且具备它的所有优点

  • 父类构造函数原型对象上的属性和方法也可以继承
  • 缺点

调用了两次父类构造函数
  • 代码

// 父类构造函数
function Parent (name, age) {
  this.name = name
  this.age = age
  this.parentLog = function () {
    return`${this.name} => ${this.age}`
  }
}

// 父类构造函数的原型对象
Parent.prototype.pProperty = 'parent prototype property'
Parent.prototype.pFunction = function () {
  return 'parent prototype function'
}

function Parent_1 (msg) {
  this.p_1_msg = msg
  this.parent_1_log = function () {
    return `${this.msg} => ${this.p_1_msg}`
  }
}

// 子类构造函数
function Child (msg, name, age) {
  // 第一次调用父类构造函数,实现多继承和传参
  Parent.call(this, name, age)
  Parent_1.call(this, 'I am parent_1\'s msg')
  this.msg = msg
  this.childLog = function () {
    return this.msg
  }
}

// 第二次调用父类构造函数,实现父类构造函数原型对象上属性和方法的继承
Child.prototype = new Parent()
Child.prototype.constructor = Child

// 子类实例一
const ins1 = new Child('child ins1', '大爹', 68)
console.log(`子类: ${ins1.childLog()} => 父类: ${ins1.parentLog()} => 父类_1: ${ins1.parent_1_log()}`)  // 子类: child ins1 => 父类: 大爹 => 68 => 父类_1: child ins1 => I am parent_1's msg

// 子类实例二
const ins2 = new Child('child ins2', '二爹', 58)
console.log(`子类: ${ins2.childLog()} => 父类: ${ins2.parentLog()} => 父类_1: ${ins2.parent_1_log()}`)  // 子类: child ins2 => 父类: 二爹 => 58 => 父类_1: child ins2 => I am parent_1's msg

// 子类无法继承父类构造函数原型对象上的属性和方法
console.log(`父类原型对象上的属性: ${ins1.pProperty}`)  // 父类原型对象上的属性: parent prototype property
console.log(`父类原型对象上的方法: ${ins1.pFunction()}`)  // 父类原型对象上的方法: parent prototype function

原型式继承

  • 缺点

和原型链继承存在一样的问题

  • 无法实现多继承
  • 子类无法单独为父类传递参数,且父类的属性值和方法会被所有子类实例所共享
  • 说明

原型链继承大同小异,因为Object.create()的本质就是Function.prototype
  • 优点

不用像原型链继承那样专门创建一个子类构造函数,直接通过Object.create()创建子对象即可
  • 代码

// 父类构造函数
function Parent (name, age) {
  this.name = name
  this.age = age
  this.parentLog = function () {
    return`${this.name} => ${this.age}`
  }
}

// 实例化父类
const parent = new Parent('老爹', 58)

// 子对象一
const ins1 = Object.create(parent, {
  msg: {
    value: 'child ins1\'s msg'
  }
})
console.log(`子类: ${ins1.msg} => 父类: ${ins1.name} => ${ins1.age}`)  // 子类: child ins1's msg => 父类: 老爹 => 58

// 子对象二
const ins2 = Object.create(parent, {
  msg: {
    value: 'child ins2\'s msg'
  }
})
console.log(`子类: ${ins2.msg} => 父类: ${ins2.name} => ${ins2.age}`)  // 子类: child ins2's msg => 父类: 老爹 => 58

寄生式继承

  • 缺点

无法实现多继承
  • 优点

不同的子对象可以为父类构造函数传递不同的参数
  • 说明

通过函数将`原型式继承`做进一步封装,对子对象的扩展都封到函数里面,调用函数返回一个对象
  • 代码

// 父类构造函数
function Parent (name, age) {
  this.name = name
  this.age = age
  this.parentLog = function () {
    return`${this.name} => ${this.age}`
  }
}

// 包装函数(工厂函数)
function createObj (msg, name, age) {
  const parent = new Parent(name, age)
  const child = Object.create(parent)
  child.msg = msg
  child.childLog = function () {
    return 'child log function'
  }
  return child
}

// 子类实例一
const ins1 = createObj('child ins1', '大爹', 68)
console.log(`子类: ${ins1.msg} => ${ins1.childLog()} => 父类: ${ins1.parentLog()}`)  // 子类: child ins1 => child log function => 父类: 大爹 => 68

// 子类实例二
const ins2 = createObj('child ins2', '二爹', 58)
console.log(`子类: ${ins2.msg} => ${ins2.childLog()} => 父类: ${ins2.parentLog()}`)  子类: child ins2 => child log function => 父类: 二爹 => 58

寄生组合式继承

  • 说明

算是javascript最优秀一种继承方式了,构造函数继承 + 原型式继承,没有了构造函数继承和原型式继承的缺点,算是对组合继承的一个优化
  • 缺点

在实现多继承时,如果有多个父类构造函数原型对象上的的属性和方法需要被子类继承,现在这个继承方式它没办法实现的,当然可以想其它方法,比如;把多个父类的原型对象上需要共享的属性和方法定义成一个新的类,让子类继承,当然这种情况下父类之间就有了耦合性,通用性就下降了
  • 代码

// 父类构造函数
function Parent (name, age) {
  this.name = name
  this.age = age
  this.parentLog = function () {
    return`${this.name} => ${this.age}`
  }
}

// 父类构造函数的原型对象
Parent.prototype.pProperty = 'parent prototype property'
Parent.prototype.pFunction = function () {
  return 'parent prototype function'
}

function Parent_1 (msg) {
  this.p_1_msg = msg
  this.parent_1_log = function () {
    return `${this.msg} => ${this.p_1_msg}`
  }
}

// 子类构造函数
function Child (msg, name, age) {
  // 第一次调用父类构造函数,实现多继承和传参
  Parent.call(this, name, age)
  Parent_1.call(this, 'I am parent_1\'s msg')
  this.msg = msg
  this.childLog = function () {
    return this.msg
  }
}

// 此处与“组合继承”不同,父类构造函数没有发生二次调用,算是对组合继承的一个优化,其实这里有个问题,如果Parent_1的原型对象上也定义了需要被继承的属性和方法呢?
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

// 子类实例一
const ins1 = new Child('child ins1', '大爹', 68)
console.log(`子类: ${ins1.childLog()} => 父类: ${ins1.parentLog()} => 父类_1: ${ins1.parent_1_log()}`)  // 子类: child ins1 => 父类: 大爹 => 68 => 父类_1: child ins1 => I am parent_1's msg

// 子类实例二
const ins2 = new Child('child ins2', '二爹', 58)
console.log(`子类: ${ins2.childLog()} => 父类: ${ins2.parentLog()} => 父类_1: ${ins2.parent_1_log()}`)  // 子类: child ins2 => 父类: 二爹 => 58 => 父类_1: child ins2 => I am parent_1's msg

// 子类无法继承父类构造函数原型对象上的属性和方法
console.log(`父类原型对象上的属性: ${ins1.pProperty}`)  // 父类原型对象上的属性: parent prototype property
console.log(`父类原型对象上的方法: ${ins1.pFunction()}`)  // 父类原型对象上的方法: parent prototype function

ES6继承

  • 说明

继承的核心是寄生组合式继承
  • 代码

// 父类
class Parent {
  constructor (name, age) {
    this.name = name
    this.age = age
  }
  parentLog () {
    return`${this.name} => ${this.age}`
  }
}

// 子类
class Child extends Parent {
  constructor (msg, name, age) {
    super(name, age)
    this.msg = msg
  }
  childLog () { 
    return this.msg
  }
}

// 实例一
// 子类实例一
const ins1 = new Child('child ins1', '大爹', 68)
console.log(`子类: ${ins1.childLog()} => 父类: ${ins1.parentLog()}`)  // 子类: child ins1 => 父类: 大爹 => 68

// 子类实例二
const ins2 = new Child('child ins2', '二爹', 58)
console.log(`子类: ${ins2.childLog()} => 父类: ${ins2.parentLog()}`)  // 子类: child ins2 => 父类: 二爹 => 58
阅读 100

推荐阅读