JavaScript继承(六)——寄生组合式继承

JavaScript继承(三)——组合继承中讲到,组合继承是JavaScript中最常用的继承模式,但是它也有自己的不足之处,现在我们就来剖析它的不足,如下示例:

function Human(name){
  this.name = name;
  this.colors = ['white', 'yellow', 'black'];
}
Human.prototype.sayName = function(){
  return this.name;
}

function Person(name, job){
  //继承属性
  Human.call(this, name);
  //定义自己的属性
  this.job = job;
}
//继承方法
Person.prototype = new Human();
Person.prototype.constructor = Person;
//定义自己的方法
Person.prototype.sayJob = function(){
  return this.job;
}

let p = new Person('bob', 'JavaScript');
console.log(p.sayName());//bob

使用组合继承让Person继承Human实际上分为两步:

  1. Human.call(this, name)继承属性。
  2. Person.prototype = new Human()继承方法。

但是在第二步中,当我们为了继承方法而创建父类型的实例时,执行了一遍构造函数,将属性也初始化了,如下图所示:

然而并不需要这样做,我们是想使用原型上的方法,属性的继承完全可以留待new Person('bob', 'JavaScript')时通过Human.call(this, name)来调用父类型的构造函数,这里new Human()等于事先多调用了一次Human的构造函数,影响了代码的效率。

再者,即便父类型已经初始化了属性,待将来创建子类型的对象,子类型对象的属性也会屏蔽父类型已经初始化好的属性,也就是说new Human()初始化的属性根本就没什么用处,如下图所示:

p上的colorsname会屏蔽掉原型上的colorsname, 那么有没有一种方案可以实现只使用原型上的方法,而不用调用父类型的构造函数呢?方案是有的,这就是本篇文章要讲的寄生组合式继承。

我们可以直观地想一下,既然只是想使用原型上的方法,不调用构造函数,那么直接将父类型的原型赋给子类型的原型不就可以了吗?像下面这样:

//继承方法
Person.prototype = Human.prototype;

表面上看是没什么问题,假如要用子类型对象调用sayName方法时,对象上没有,于是到原型去找,原型就是Human的原型,于是找到了sayName方法,成功调用。但是结合原型链和对象深浅复制的知识(可以参考JavaScript继承(一)——原型链JavaScript中对象的浅复制和深复制),Human本身就是对象,Human.prototype其实是Human原型对象的引用,Person.prototype = Human.prototype就是引用的复制,结果就是Person.prototypeHuman.prototype指向同一个对象——Human的原型对象,而原型对象的constructor属性是指向函数的,那么现在我们让Human原型对象的constructor属性指向谁呢?Human还是Person?显然原型链被打乱了。想起了哪吒的三头六臂,要是再有一个constructor属性就好了。

这时原型式继承又派上用场了,如下所示:

//继承方法
Person.prototype = object(Human.prototype);
Person.prototype.constructor = Person;

我们可以使用原型式继承创建Human原型对象的一个子对象,这个子对象赋给Person.prototype,改变这个子对象的constructorPerson,这样既可以达到Person的原型继承Human的原型的效果,同时还不必修改Human原型的constructor属性,也不需要调用Human的构造函数,完美地解决了我们的问题。

下面我们来看下现在的原型链图:

我们可以定义一个函数来实现这个逻辑:

function inheritPrototype(subType, superType){
  subType.prototype = Object.create(superType.prototype);
  subType.prototype.constructor = subType;
}

于是上面的组合继承可以改写为:

function Human(name){
  this.name = name;
  this.colors = ['white', 'yellow', 'black'];
}
Human.prototype.sayName = function(){
  return this.name;
}

function Person(name, job){
  //继承属性
  Human.call(this, name);
  //定义自己的属性
  this.job = job;
}
//继承方法
inheritPrototype(Person, Human);
//定义自己的方法
Person.prototype.sayJob = function(){
  return this.job;
}

let p = new Person('bob', 'JavaScript');
console.log(p.sayName());//bob

继承方法时使用我们自己封装的inheritPrototype方法,这就是寄生组合式继承。

寄生组合式继承的高效率体现在它只调用了一次父类型的构造函数,并且因此避免了在子类型的原型上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceofisPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承模式。

转载于:https://my.oschina.net/bob1900/blog/3010873

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值