JavaScript繼承(六)——寄生組合式繼承

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實際上分爲兩步:java

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

可是在第二步中,當咱們爲了繼承方法而建立父類型的實例時,執行了一遍構造函數,將屬性也初始化了,以下圖所示:函數

然而並不須要這樣作,咱們是想使用原型上的方法,屬性的繼承徹底能夠留待new Person('bob', 'JavaScript')時經過Human.call(this, name)來調用父類型的構造函數,這裏new Human()等於事先多調用了一次Human的構造函數,影響了代碼的效率。this

再者,即使父類型已經初始化了屬性,待未來建立子類型的對象,子類型對象的屬性也會屏蔽父類型已經初始化好的屬性,也就是說new Human()初始化的屬性根本就沒什麼用處,以下圖所示: .net

p上的colorsname會屏蔽掉原型上的colorsname, 那麼有沒有一種方案能夠實現只使用原型上的方法,而不用調用父類型的構造函數呢?方案是有的,這就是本篇文章要講的寄生組合式繼承。prototype

咱們能夠直觀地想一下,既然只是想使用原型上的方法,不調用構造函數,那麼直接將父類型的原型賦給子類型的原型不就能夠了嗎?像下面這樣:3d

//繼承方法
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屬性就行了。code

這時原型式繼承又派上用場了,以下所示:對象

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

咱們可使用原型式繼承建立Human原型對象的一個子對象,這個子對象賦給Person.prototype,改變這個子對象的constructorPerson,這樣既能夠達到Person的原型繼承Human的原型的效果,同時還沒必要修改Human原型的constructor屬性,也不須要調用Human的構造函數,完美地解決了咱們的問題。blog

下面咱們來看下如今的原型鏈圖:

咱們能夠定義一個函數來實現這個邏輯:

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()。開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承模式。

相關文章
相關標籤/搜索