js中的几种继承

263 阅读4分钟

前言

此文章只为记录分享本人的一些浅薄理解,同时希望可以给一些困惑的朋友些许启发。

原型链

说到继承就不得不先说说原型链
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。(此段出自《JavaScript 高级编程设计》)
总结:原型链是一种关系,原型与实例的一种指向关系,它可以是一级,也可以是多级。

继承的方法

每一个对象都会有个一个 __proto__ 属性,每一个函数都会有个 prototype 属性。
构造函数可以通过 prototype 查找它的原型,对象可以通过 __proto__ 查找它的原型。

1.原型链继承

// 超类
function SuperType () {
  this.property = true
}
SuperType.prototype.getSuperValue = function () {
  return this.property
}

// 子类
function SubType () {
  this.subproperty = false
}

// 继承了 SuperType
SubType.prototype = new SuperType()

原型链继承的缺点:
1、当我们修改继承自父类的属性时,会使其他实例的该属性也一起被修改了。
2、在创建子类型的实例时,不能向超类型的构造函数中传递参数。

2.构造函数继承

解决了原型链继承中修改继承自父类属性时,影响了其他实例的数据问题

// 超类
function SuperType () {
  this.colors = ['red', 'blue', 'green']
}

function SubType () {
  // 继承了 SuperType
  SuperType.call(this)
}

var instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors)
// 'red,blue,green,black'

var instance2 = new SubType()
console.log(instance2.colors)
// 'red,blue,green'

解决了子类型不能向父类型传递参数的问题

// 超类
function SuperType (name) {
  this.name = name
}

// 子类型
function SubType () {
  SuperType.call(this, 'yzh')
  
  this.age = 18
}

var instance = new SubType()
console.log(instance.name)
// 'yzh'
console.log(instance.age)
// '18'

构造函数继承的缺点:
1、方法都在构造函数中定义,函数无法复用

3.组合继承

组合继承又称为伪经典继承,指的是将原型链和构造函数继承组合到一起,从而发挥二者之长的一种继承模式。

// 父类
function SuperType (name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function () {
  console.log(this.name)
}

// 子类
function SubType (name, age) {
  // 继承属性
  SuperType.call(this, name)
  
  this.age = age
}

// 继承方法
SubType.prototype = new SuperType()
// 此处将 constructor 重新指向了 SubType,否则会指向 SuperType
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function () {
  console.log(this.age)
}

var instance1 = new SubType('ye', 18)
instance1.colors.push('black')
console.log(instance1.colors) // 'red,blue,green,black'
instance1.sayName() // 'ye'
instance1.sayAge() // 18

var instance2 = new SubType('zh', 20)
console.log(instance2.colors) // 'red,blue,green'
instance2.sayName() // 'zh'
instance2.sayAge() // 20

组合继承的缺点:
1、无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候;另一次是在子类型构造函数内部

4.原型式继承

这种方式并没有使用严格意义上的构造函数,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。

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

var person = {
  name: 'yzh',
  friends: ['天猫精灵', '小爱同学']
}

var anotherPerson1 = object(person)
anotherPerson1.name = 'zh'
anotherPerson1.friends.push('Siri')

var anotherPerson2 = object(person)
anotherPerson2.name = 'Jhon'
anotherPerson2.friends.push('小艺')

console.log(person) // '天猫精灵,小爱同学,Siri,小艺'

Object.create() 方法规范了原型式继承,此方法将传入的对象属性进行了一次浅拷贝

5.寄生式继承

function createAnother (original) {
  var clone = object(original)
  clone.sayHi = function () {
    console.log('hi')
  }
  return clone
}

和构造函数模式一样,不能做到函数复用而降低效率

6.寄生组合式继承

核心思想:解决组合继承中调用两次超类型构造函数的问题

// 解决组合继承中第一次调用 SuperType 超类型
function inheritPrototype (subType, superType) {
  var prototype = object(superType.prototype) // 创建对象
  subType.constructor = subType // 增强对象
  subType.prototype = prototype // 指定对象 
}

// 超类型
function SuperType (name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
  console.log(this.name)
}

// 子类型
function SubType (name, age) {
  SuperType.call(this, name)
  
  this.age = age
}

inheritPrototype(SubType, SuperType)

SubType.prototype.sayAge = function () {
  console.log(this.age)
}

7.class继承

class继承使用 ES6 新增的语法 extends 来实现继承,它比上述的继承方法更清晰、方便。

// 超类型
class SuperType {
  constructor (name) {
    this.name = name
    this.colors = ['red', 'blue', 'green']
  }

  sayName () {
    console.log(this.name)
  }
}

// 子类型
class SubType extends SuperType {
  constructor (name, age) {
    super(name)
    this.name = name
    this.age = age
  }

  sayAge () {
    console.log(this.age)
  }
}

var instance1 = new SubType('ye', 18)
instance1.colors.push('black')
console.log(instance1.colors) // 'red,blue,green,black'
instance1.sayName() // 'ye'
instance1.sayAge() // 18

var instance2 = new SubType('zh', 20)
console.log(instance2.colors) // 'red,blue,green'
instance2.sayName() // 'zh'
instance2.sayAge() // 20