在原型链继承中,因为父类属性都是通过原型链访问到的,所以面临着无法赋值且更改会影响到所有子类的问题,红宝书在其后也给出了几种解决办法
盗用构造函数
盗用构造函数的方法用一句话形容就是在子类中调用父类的构造函数。
function SuperType(type){
this.location = type
this.attr = {name:'superAttr'}
}
SuperType.prototype.getSuperValue = function(){ return this.location}
function SubType(subType,superType){
this.sublocation = subType
SuperType.call(this, superType)
}
SubType.prototype.getSubValue = function(){ return this.sublocation }
let instance = new SubType('subType', 'superType')
let instance2 = new SubType('subType2', 'superType2')
console.log('instance attr is',instance.attr.name) //instance attr is superAttr
console.log('instance2 attr is',instance2.attr.name) //instance attr is superAttr
instance.attr.name='SuperAttribute'
console.log('instance attr is',instance.attr.name) //instance attr is superAttribute
console.log('instance2 attr is',instance2.attr.name) //instance attr is superAttr
在看到这段代码时不禁思考,为什么一定要用call或者apply呢?深究这个问题的本质是,如果直接执行构造函数呢?又或者new关键字究竟执行了什么?
call和apply
我使用dir输出了SubType构造函数
可以看到call和apply是该构造函数原型上的方法,因为构造函数也是函数,所以这两个方法就是函数原型上的方法。他们间的继承关系大致如下图所示:
因此,当我使用关键字Function时就创建了Function的实例,当然,该实例上的__proto__属性指向了Function.prototype。
call和apply的作用类似,第一个参数都为一个this值,作用是在该this的作用域内执行该函数。
而我刚刚在子构造函数中调用父类构造函数SuperType.call(this, superType) ,就是在子构造函数的作用域中执行父类构造函数。
继续思考一步,如果不用new关键字,直接执行构造函数会发生什么呢?
new关键字
我又改造了一个小demo
function SubType(subType){
this.sublocation = subType
}
SubType('Who are you?')
分析下这段代码可能的执行过程,当执行SubType时,this会指向全局作用域window(是的,我在html中引用了该js文件),而this.sublocation = subType就是在window中添加了一个名为 sublocation的属性,并将值设为'Who are you?' ,不妨打印window看一下
果然如此,如果不用new关键字,就会直接在window中添加一个属性。
如果此时在SubType中使用 SuperType.call(this) 呢?
在使用new关键字时,js中一共做了四件事:
- 创建一个空的简单 JavaScript 对象(即
{}); - 为步骤 1 新创建的对象添加属性
__proto__,将该属性链接至构造函数的原型对象; - 将步骤 1 新创建的对象作为
this的上下文,并执行函数; - 如果该函数没有返回对象,则返回
this。
在新建对象中执行构造函数也就会将构造函数内部的属性赋值给新的对象,因此在SubType中执行SuperType.call(this, superType) 就会将SuperType中的属性放到SubType的实例中,且这些实例与SuperType不再有强链接,更改父类的属性也不会影响到其他子类。
盗用构造函数的局限性
先整体的看下盗用构造函数实现的继承,我可以给父类传值,父类的属性也会被继承,修改属性也不会影响到其他实例
由图中的继承关系可以看出来,我是没办法访问到父类原型上的方法的,且因为prototype的丢失,也无法用instanceof判断出父类。虽然可以继承父类的属性,但代价未免有些大了。这种缺陷可以说是new关键字造成的,也可以说是构造函数模式本身造成的,如何解决这种缺陷呢?
组合继承
组合继承就是组合了原型链继承和盗用构造函数继承,在子类的构造函数中执行父类,父类的实例作为子类构造函数的原型对象,这时不仅可以访问到父类原型上的方法,也可以彻底的继承父类的属性。
function SuperType(type){
this.location = type
this.attr = {name:'superAttr'}
}
SuperType.prototype.getSuperValue = function(){ return this.location}
function SubType(subType,superType){
this.sublocation = subType
SuperType.call(this,superType)
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function(){ return this.sublocation }
let instance = new SubType('subType', 'superType')
let instance2 = new SubType('subType2', 'superType2')
console.dir(instance)
console.log('instance attr is',instance.attr.name) //instance attr is superAttr
console.log('instance2 attr is',instance2.attr.name) //instance attr is superAttr
instance.attr.name='SuperAttribute'
console.log('instance attr is',instance.attr.name) //instance attr is superAttribute
console.log('instance2 attr is',instance2.attr.name) //instance attr is superAttr
具体的继承关系如下图所示:
但使用原型链的方法 依然破坏了原型对象本身的contructor属性(虽然可以手动给加上),所以依然不够完美,想要更加完美,需要他山之石。