原型链继承

基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法,也就是将父类的实例作为子类的原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Super() {
this.superproperty = true
}

Super.prototype.getSuperValue = function () {
console.log(this.superproperty)
}

function Sub() {
this.subproperty = false
}

Sub.prototype = new Super()

Sub.prototype.getSubValue = function () {
console.log(this.subproperty)
}

可以看到,这种方式实现的继承本质上是重写原型对象,上面的代码中,继承是通过创建Super的实例,并将该实例赋给Sub.prototype,因此,原本存在于Super实例中的所有属性和方法,现在也存在于Sub.prototype中了

1
2
3
4
5
6
7
8
9
new Sub()
├── subproperty: false
└── __proto__
├── getSubValue: f()
├── superproperty: true
└── __proto__
├── getSuperValue: f()
├── constructor: f Super()
└── __proto__: Object.prototype

很明显,getSuperValue()方法依然还在Super.prototype中,但superproperty则位于Sub.prototype中,这是因为superproperty是一个实例属性,而getSuperValue()是原型方法

同时,obj.constructor现在指向Super,这是由于Sub.prototype被重写了的缘故

总结:在通过原型链实现继承的情况下,实例是子类的实例也是父类的实例,父类新增的原型方法和属性,子类都能够访问,并且原型链继承简单易于实现,缺点是来自原型对象的所有属性被所有实例共享,无法向父类构造函数传参

构造继承

基本思想:在子类构造函数的内部调用超类的构造函数

1
2
3
4
5
6
7
8
9
10
11
function Super() {
this.superproperty = 'hello'
this.showValue = function () {
console.log(this.superproperty)
}
}

function Sub(property) {
Super.call(this)
this.subproperty = property
}

这种方式实际上是在将要创建的Sub实例的环境下调用Super构造函数,于是会在Sub实例中执行Super定义的所有对象初始化代码,因此,每个Sub实例会有一个来自Super的属性和方法的副本

1
2
3
4
5
let obj = new Sub('world')
let obj1 = new Sub('world')
console.log(obj.subproperty) // => 'world'
obj.showValue() // => 'hello'
console.log(obj.showValue === obj1.showValue) // => false
1
2
3
4
5
6
7
new Sub()
├── showValue: f()
├── subproperty: 'world'
├── superproperty: 'hello'
└── __proto__
├── constructor: f Sub()
└── __proto__: Object.prototype

如果只使用构造继承,那么不可避免的会遇到构造函数模式存在的问题,即公有属性和方法无法复用,而且,在超类原型中定义的方法,子类也无法使用

1
2
3
4
5
6
Super.prototype.getSuperValue = function () {
console.log(this.superproperty)
}

obj.getSuperValue()
// Uncaught TypeError: obj.getSuperValue is not a function

组合继承

基本思想:将原型链和构造继承结合在一起,使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name, age) {
this.name = name
this.age = age
}

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

function Programmer(skill, name, age) {
this.skill = skill
Person.call(this, name, age)
}

Programmer.prototype = new Person()
Programmer.prototype.constructor = Programmer
Programmer.prototype.saySkill = function () {
console.log(this.skill)
}

使用这种模式既能通过在原型上定义方法实现函数的复用,同时能保证每个实例有自己的属性

1
2
3
4
5
6
7
let p1 = new Programmer('js', 'Mike', 22)
p1.sayName() // => Mike
p1.saySkill() // => js
console.log(p1 instanceof Programmer) // => true

let p2 = new Programmer('java', 'Tom', 26)
console.log(p1.sayName === p2.sayName) // => true
1
2
3
4
5
6
7
8
9
10
p1: new Programmer()
├── age: 22
├── name: 'mike'
├── skill: 'js'
└── __proto__
├── age: undefined
├── constructor: f Programmer()
├── name: undefined
├── saySkill: f()
└── __proto__: Object.prototype

组合式继承的缺点在于必须调用两次父类的构造函数,生成两份实例

原型式继承(Object.create)

基本思想:借助原型,基于已有的对象创建新对象

1
2
3
4
5
function inherit(obj) {
function F() {}
F.prototype = obj
return new F()
}

inherit函数内部,先创建一个临时的构造函数,然后将传入的对象作为构造函数的原型,最后返回这个构造函数的一个实例

本质上,inherit对传入其中的对象执行了一次浅复制

1
2
3
4
5
6
7
8
9
10
11
12
13
var person = {
name: 'Mike',
age: 22
}

var p1 = inherit(person)
p1.name = 'Tom'
p1.age = '26'
p1.sayName = function () {
console.log(this.name)
}

p1.sayName() // => 'Tom'
1
2
3
4
5
6
7
8
p1
├── age: 22
├── name: 'Tom'
├── sayName: f()
└── __proto__
├── age: 22
├── name: 'Mike'
└── __proto__: Object.prototype

原型式继承要求必须有一个对象可以作为另一个对象的基础,然后再根据具体需求对得到的对象加以修改

寄生式继承

基本思路:与原型式继承紧密相关,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式增强对象

1
2
3
4
5
6
7
8
9
10
11
12
13
function inherit(obj) {
function F() {}
F.prototype = obj
return new F()
}

function createAnother(obj) {
let clone = inherit(obj)
clone.sayHi = function () {
console.log('hi')
}
return clone
}
1
2
3
4
5
6
7
8
var person = {
name: 'Mike',
age: 22
}

var p1 = createAnother(person)

p1.sayHi() // 'hi'
1
2
3
4
5
6
p1
├── sayHi: f()
└── __proto__
├── age: 22
├── name: 'Mike'
└── __proto__: Object.prototype

寄生组合继承

基本思想:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法

不必为了指定子类型的原型而调用超类的构造函数,我们需要的只是超类型原型的一个副本,本质上,就是使用寄生式继承来继承超类的原型,然后再将结果指定给子类型的原型

1
2
3
4
5
6
7
8
9
10
11
function inherit(obj) {
function F() {}
F.prototype = obj
return new F()
}

function inheritPrototype(subType, superType) {
let prototype = inherit(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Person(name, age) {
this.name = name
this.age = age
}

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

function Programmer(skill, name, age) {
this.skill = skill
Person.call(this, name, age)
}

inheritPrototype(Programmer, Person)

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

let p1 = new Programmer('java', 'Jack', 30)
p1.sayName() // => 'Jack'
p1.sayAge() // => 30
1
2
3
4
5
6
7
8
p1
├── age: 30
├── name: 'Jack'
├── skill: 'java'
└── __proto__
├── constructor: f Programmer()
├── sayAge: f()
└── __proto__: Object.prototype

可以看到,通过这种方法只需要调用一次父类的构造函数