JavaScript 继承

10/17/2022 JavaScript

# 原型链继承

ECMAScript主要继承方式,通过原型继承多个引用类型的属性和方法。

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

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

let subType = new SubType()
console.log(subType.getSuperValue()) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

SubType 继承 SuperType,而 SuperType 继承 Object。在调用 instance.toString()时,实际上调用的是保存在 Object.prototype 上的方法。

存在问题:如果原型链上存在引用值,将会影响所有实例。

# 构造函数继承

盗用构造函数,在子类构造函数调用父类构造函数。

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

function SubType() {
  SuperType.call(this)
}
const instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors); // ['red', 'green', 'blue', 'black']

const instance2 = new SubType()
console.log(instance2.colors); // ['red', 'green', 'blue']
1
2
3
4
5
6
7
8
9
10
11
12
13

存在问题:盗用构造函数子类无法获取父类原型上定义的方法

# 组合继承

综合了原型链继承和构造函数继承,使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。

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()
SubType.prototype.sayAge = function () {
  console.log(this.age)
}

let instance1 = new SubType('Hello', 18)
instance1.colors.push('black')
console.log(instance1.colors) // ['red', 'blue', 'green', 'black']
instance1.sayName() // 'Hello'
instance1.sayAge() // 18

let instance2 = new SubType('World', 20)
console.log(instance2.colors) // ['red', 'blue', 'green']
instance2.sayName() // 'World'
instance2.sayAge() // 20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

组合继承弥补了原型链和盗用构造函数的不足,是 Javascript 中使用最多的继承模式。而且组合继承保留了 instanceof 和 isPrototypeOf()。

存在问题:效率问题,父类构造函数始终会被调用两次:一次是在创建子类原型时调用,另一次是在子类构造函数中调用。

# 原型式继承

通过创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。

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

let person = {
  name: 'person0',
  friends: ['person1', 'person2']
}
let anotherPerson = object(person)
anotherPerson.friends.push('person3')

let yetAnotherPerson = object(person)
yetAnotherPerson.name = 'person4'
yetAnotherPerson.friends.push('person5')

console.log(person.name) // 'person0'
console.log(person.friends) // ['person1', 'person2', 'person3', 'person5']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在只有一个参数时,object.create() 与这里的 object() 方法效果相同。

let person = {
  name: 'person0',
  friends: ['person1', 'person2']
}
let anotherPerson = Object.create(person)
anotherPerson.friends.push('person3')

let yetAnotherPerson = Object.create(person)
yetAnotherPerson.name = 'person4'
yetAnotherPerson.friends.push('person5')

console.log(person.name) // 'person0'
console.log(person.friends) // ['person1', 'person2', 'person3', 'person5']
1
2
3
4
5
6
7
8
9
10
11
12
13

存在问题:本质上,object() 是对传入的对象执行了一次浅拷贝。

# 寄生式继承

寄生式继承的思路类似于寄生构造函数和工厂模式,创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。

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

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

let person = {
  name: 'person0',
  friends: ['person1', 'person2']
}
let anotherPerson = createAnother(person)
console.log(anotherPerson.name) // 'person0'
anotherPerson.sayHi() // 'hi'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

存在问题:通过寄生式继承给对象添加函数会导致函数难以重用。

# 寄生式组合继承

基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。

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

function inheritPrototype(parent, child) {
  let prototype = object(parent)
  prototype.constructor = child
  child.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(SuperType, SubType)
SubType.prototype.sayAge = function () {
  console.log(this.age)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

这里只调用了一次 SuperType 的构造函数,提高了效率。而且,原型链仍然保持不变,因此 instanceof 操作符和 isPrototypeOf() 方法正常有效。寄生式组合继承可以算是引用类型继承的最佳模式。

Last Updated: 10/22/2022, 1:02:04 AM