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
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']
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
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']
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']
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'
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)
}
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() 方法正常有效。寄生式组合继承可以算是引用类型继承的最佳模式。