JavaScript 之继承

69 阅读4分钟

原型链继承

原型链继承的本质就是重写原型对象,通过原型搜索找到共享的属性和方法。不过原型链的缺点在于实例对于引用类型的修改将影响其它实例。

function SuperType() {
  this.property = true
  this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.getSuperValue = function () {
  return this.property
}

function SubType() {
  this.subProperty = false
}

// 这里是关键,创建SuperType的实例,并将该实例赋值给SubType.prototype
SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function () {
  return this.subProperty
}

var instance1 = new SubType()
console.log(instance1.getSuperValue()) // true
instance1.colors.push('grey')
console.log(instance1.colors) // ['red', 'blue', 'green', 'grey]

var instance2 = new SubType()
console.log(instance2.getSuperValue()) // true
console.log(instance2.colors) // ['red', 'blue', 'green', 'grey']

借用构造函数

在子类型构造函数的内部调用超类型构造函数。可以在通过子类型构造函数向超类型构造函数传参

function SuperType(name) {
  this.name = name
  this.color = ['red', 'green', 'blue']

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

  SuperType.prototype.job = 'Athletes'

}
function SubType(name,age) {
  //继承了 SuperType,传递参数
  SuperType.call(this,name)

  this.age = age
}
var instance1 = new SubType('jack',20)
console.log(instance1.name);  // jack
console.log(instance1.age); // 20
// instance1.sayName() // instance1.sayName is not a function
console.log(instance1.job);  // undefined

通过代码 SuperType.call(this,name) 调用了SuperType构造函数,并传递了name,将SuperType的属性设置在了SubType的实例上.在SuperType原型上定义的属性和方法,在SubType的实例上不存在。

image.png

缺点:

  • 只能继承父类的实例属性和方法,不能继承原型属性和方法
  • 无法实现复用,每个子类都有父类实例函数的副本

组合继承

组合继承是将原型链和借用构造函数组合使用的一种继承模式。使用原型链实现对原型属性和方法的继承,然后通过构造函数来实现对实例属性的继承。这样就保证了在原型上定义方法实现函数复用,然后每个实例都有自己的属性。

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

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

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

// 继承方法
SubType.prototype = new SuperType()
// 重写 SubType.prototype 的 constructor 属性,指向自己的构造函数 SubType.
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function () {
  console.log(this.age)
}

var instance1 = new SubType('Nicholas', 29)
instance1.colors.push('black')
console.log(instance1.colors) // ['red', 'blue', 'green', 'black']
instance1.sayName() // Nicholas
instance1.sayAge() // 29

var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // ['red', 'blue', 'green']
instance2.sayName(); // Greg
instance2.sayAge(); // 27

image.png

  1. SuperType 构造函数中定义了两个属性:name 和 colors
  2. SuperType 的原型上定义了方法 sayName()
  3. SubType 的原型设置为 SuperType 的实例,将 SuperType 实例属性添加到了 SubType 的原型上
  4. SubType 的原型上添加属性 constructor 为 SubType,指向自己的构造函数 SubType。因为上一步导致SubType 的原型不具有 constructor 属性。
  5. SubType 的原型上定义方法 sayAge()
  6. 实例化 SubType ,SuperType.call(this, name) 将 SuperType 实例属性添加到了 SubType 的实例上

缺点:

  1. 调用两次超类型构造函数
  2. 子类型会包含超类型对象的全部实例属性

原型式继承

定义一个函数接受一个对象参数,在函数内部创建一个构造函数,设置构造函数的原型是传参,在返回构造函数的实例。

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

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van'],
}

var p1 = object(person)

p1.name = 'Greg'
p1.friends.push('Rob')

var p2 = object(person)
p2.name = 'Linda'
p2.friends.push('Barbie')

console.log(person.friends);  // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']
console.log(p1.friends);  // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']
console.log(p2.friends);  // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']

缺点:

  1. 原型属性中引用类型属性修改将影响其它实例

ECMAScript5 中存在 Object.create() 方法,可以代替这个

寄生式继承

创建对象,然后在函数内部增强,然后再返回这个对象。

function createObject(original) {
  var clone = Object(original) // 通过 Object() 函数创建一个新对象
  clone.sayHi = function () {
    // 以某种方式来增强对象
    console.log('hi')
  }
  return clone // 返回这个对象
}

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van'],
}
var p1 = createObject(person)
p1.sayHi() // hi

缺点:

  1. 原型属性中引用类型属性修改将影响其它实例

寄生组合式继承

使用寄生式继承来继承超类型的原型,在将结果指定给子类型的原型

inheritPrototype() 方法只是让子类继承超类的原型,超类的属性是通过调用 SuperType构造函数来继承的

function inheritPrototype(subType, superType) {
  var prototype = Object.create(superType.prototype) // 创建对象,创建超类原型的一个副本
  prototype.constructor = subType // 增强对象,弥补因重写原型而失去的默认的 constructor 属性
  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)
}

var instance1 = new SubType('Nicholas', 23)
var instance2 = new SubType('Greg', 23)

instance1.colors.push('yellow')
instance2.colors.push('black')

console.log(instance1.colors); // ['red', 'blue', 'green', 'yellow']

console.log(instance2.colors);  // ['red', 'blue', 'green', 'black']