继承模式作为JavaScript学习的重点,也是面试中必不可少的考点,但是继承模式时常会把我们绕晕。本文将通过我自己的理解,并用代码结合图片的形式来讲解JavaScript中几种继承模式的区别。
原型链概述
JavaScript通过原型链实现继承,故在讲解继承模式之前,先简单的讲解构造函数和实例之间的联系(原型链的一部分知识)。我们从构造函数说起,对于一个构造函数来说,它有一个prototype属性,而这个属性有个叫做constructor的属性,这个属性指向构造函数本身。对于一个实例来说,他有一个__proto__属性,这个属性指向构造函数的prototype。举个例子来说的话
function Person(){
this.age = 11;
}
let person = new Person();
对于这样一段代码,可以用下图来表示构造函数和实例之间的关系:
关于原型链的更详细讲解,可以移步冴羽大佬的这篇文章 JavaScript深入之从原型到原型链
继承方式
接下来就到了本文的重头戏:继承模式。 继承方式主要分为以下几种:
- 原型链继承
- 借用构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
各个继承方式的关系图解如下:
由图可知,组合继承的基础是原型链继承和借用构造函数继承,寄生组合式继承的基础是寄生式继承和借用构造函数继承。接下来将详细介绍各个继承方式。
另外,本文的侧重点主要是各个继承模式是什么,所以每个继承模式的优缺点和为什么会出现这种继承方法不做过多的展开。
原型链继承(修改原型链)
正如我括号里所写的,原型链继承简单来说就是修改原型链。举个例子:
function SuperType(){
this.name = 'supertype'
this.val = 'super'
}
function SubType(){
this.name = 'subtype'
}
SuperType.prototype.sayName = function (){
console.log(this.name)
}
SubType.prototype = new SuperType() // 修改原型链,使SubType继承了SuperType的原型链
let instance = new SubType()
console.log(instance.name)
console.log(instance.val)
instance.sayName()
// > subtype
// > super
// > subtype
SubType的实例instance在查找属性的时候,如果本身没有这个属性,就会一直往原型链上查找,直到找不到为止,例子中由于SubType继承了SuperType的原型链,所以instance拥有一些本不属于SubType的属性。其查找过程如下:
借用构造函数继承
借用构造函数的作用就是“借用属性”。理解这一个继承模式的要点就是时刻提醒自己,构造函数也是一个函数。
例子如下:
function SuperType(){
this.name = 'supertype'
this.val = 'super'
}
function SubType(){
SuperType.call(this)
// 相当于执行了这么一段函数
// this.name = 'supertype'
// this.val = 'super'
this.name = 'subtype'
}
let instance = new SubType()
console.log(instance)
// > {"name":"subtype","val":"super"}
如果能够时刻清楚,构造函数也是一个函数,那么这个例子就不难理解了。可以看到,这个继承模式只继承了属性,并没有继承原型链。
组合继承
组合继承就是由借用构造函数继承和原型链继承组合而成的。简单来说就是一个继承属性,一个继承原型链。例子如下:
function SuperType(){
this.name = 'supertype'
this.val = 'super'
}
function SubType(){
SuperType.call(this)
this.name = 'subtype'
}
SubType.prototype = new SuperType()
由图和代码可以看出组合继承调用了两次超类型构造函数 SuperType ,第一次是在重写子类型构造函数原型,第二次是在子类型构造函数内部。并且在图中有两组name,val属性一组在实例上一组在SubType原型中。
原型式继承
原型式继承与上面的几种继承方式不同,这种继承方式是继承对象。来看例子:
function object(o){
function F(){}
F.prototype = o;
return new F()
}
let person = {
age: 12,
name: 'soma'
}
let anotherPerson = object(person)
console.log(anotherPerson)
console.log(anotherPerson.__proto__)
// > {}
// > {"age":12,"name":"soma"}
原型式继承的关键就是这个 object 函数,它接受一个目标对象,并返回一个对象,这个对象的 __proto__ 为这个目标对象。
文字可能有点抽象,还是来看图吧。
可以看到对于一个相同的目标对象多次调用 object 函数的话,他们返回的实例对象的 __proto__ 是一样的,换言之,当这些实例对象修改他们自身没有,但是 __proto__ 上有的属性时,是会相互影响的。
寄生式继承
寄生式继承是原型式继承的加强版。例子如下;
function object(o){
function F(){}
F.prototype = o;
let obj = new F()
obj.sayHi = function (){ //加强这个obj
console.log('hi~')
}
return obj
}
let person = {
age: 12,
name: 'soma'
}
let anotherPerson = object(person)
anotherPerson.sayHi()
console.log(anotherPerson.__proto__.sayHi)
// > hi~
// > undefined
相比于原型式继承,寄生式继承多了一步加强实例对象的步骤。图解如下:
寄生组合式继承
寄生组合式继承基本上就是集大成者的继承方法了,综合了原型式继承,借用构造函数继承,寄生式继承。例子如下:
function object(o){
function F(){}
F.prototype = o;
return new F()
}
function inheritPrototype(subtype, supertype){
let prototype = object(supertype.prototype)
prototype.constructor = subtype
subtype.prototype = prototype
}
function SuperType(){
this.name = 'supertype'
this.val = 'super'
}
function SubType(){
SuperType.call(this)
this.name = 'subtype'
}
inheritPrototype(SubType, SuperType)
let instance = new SubType()
通过调用超类型构造函数获得了其所有属性,再通过 inheritPrototype 函数继承超类型构造函数的原型链。
图解如下:
Reference:
- JavaScript高级程序设计(第3版)
- JavaScript深入之从原型到原型链