JS继承的6中方式

183 阅读3分钟

ES5的继承

我个人所理解的js中的继承, 实现继承的方式就是将子类的prototype的__proto__指向 父类的prototype子类的prototype的constructor指向子类本身

为什么我会有这种理解, 推断如下, 首先我们都知道js中Object构造函数 是所有构造函数的超类, 如图所示

image

通过类比, 那么我们实现继承的最终目标就明确了! 超类为Animal, 子类为Dog, 只要将 两者的关系实现如下图所示, 也就实现了Dog继承了Animal

function Animal () {

}

Animal.prototype.__proto__ === Object.prototype

javascript高级程序设计中提到了六种实现继承的方式, 每种方式各有千秋, 都有其优缺点

原型链继承

缺点

  1. 无法向超类传递参数
  2. 由于构造函数的原型指向了同一个对象, 当为引用类型的对象时, 实例改变了引用类型的值, 可能会导致后面的new出来的实例与期望不一致
function Animal () {
  this.name = 'animal'
  this.colors = ['red', 'blue', 'black']
}

function Dog () {
  this.speak = 'bark'
}

// 缺点1
Dog.prototype = new Animal() // 继承的关键步骤

// { speak: 'bark', __proto__: { name: 'animal', colors: ["red", "blue", "black"] } }
var dog1 = new Dog();

// 缺点2
// { speak: 'bark', __proto__: { name: 'animal', colors: ["red", "blue", "black", "white"] } }
dog1.colors.push('white')

// { speak: 'bark', __proto__: { name: 'animal', colors: ["red", "blue", "black", "white"] } }
var dog2 = new Dog();

借用构造函数

优点

  1. 解决了原型链继承无法向超类传参的问题
  2. 属性中的引用对象也不会被修改

缺点

  1. 方法都在构造函数中定义, 函数复用性差 (此缺点我无法理解, 有厉害的掘友麻烦解释下)
  2. 实例无法获取超类原型上的属性
function Animal (name) {
  this.name = name
  this.colors = ['red', 'blue', 'black']
}

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

// 优点1
function Dog (name) {
  this.speak = 'bark'
  Animal.call(this, name)
}

var dog1 = new Dog('happy')

// 优点2
dog1.colors.push('white')

var dog2 = new Dog('sad')

// 缺点2
dog1.sayName // undefined

组合继承

优点

  1. 解决了借用构造函数法无法继承超类原型链上属性的问题

缺点

  1. 超类调用了两次, 函数的自身属性和原型上会有两份表现相同(不是同一个引用)的属性
function Animal (name) {
  this.name = name
  this.colors = ['red', 'blue', 'black']
}

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

function Dog (name) {
  this.speak = 'bark'
  Animal.call(this, name)
}

Dog.prototype = new Animal()
Dog.prototype.constructor = Dog

var dog1 = new Dog('test')

// 优点1
dog1.sayName // ƒ () { console.log(this.name) }

// 缺点1
delete dog1.colors
dog1.colors // ["red", "blue", "black"] 属性依然存在

原型式的继承

缺点

  1. 每次都需要重新创建一个函数
  2. 当传入的对象中包含引用类型的值时, 可能会改变其值
function object(o) {
  // 缺点1
  function F () {}
  F.prototype = o
  return new F()
}

const obj = { name: 'test', colors: ['red', 'blue', 'black'] }

var o1 = object(obj)
var o2 = object(obj)

o1.colors.push('white')
o2.colors // ["red", "blue", "black", "white"]

以上就是原型式的继承方式, 后来es5中提出了Object.create()将该继承方式规范化

寄生式的继承

个人感觉这种继承方式使用范围不广, 一笔带过

在主要考虑对象也不是自定义类型和构造函数的情况下, 寄生式继承也是一种有用的方式, 使用object 并不是必须的, 任何能够返回此对象的都适用于此模式。

function createObject(o) {
  let clone = object(o)
  clone.sayName = function () {
    console.log(this.name)
  }

  return clone
}

寄生组合式继承

优点

  1. 只调用了一次超类的构造函数, 避免了创建多余的属性
  2. 原型链能够保持不变
function extends (parentClass, childClass) {
  var prototype = Object.create(parentClass.prototype)
  childClass.prototype = prototype
  childClass.prototype.constructor = childClass
}