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
Human.call(this, name)
繼承屬性。Person.prototype = new Human()
繼承方法。可是在第二步中,當咱們爲了繼承方法而建立父類型的實例時,執行了一遍構造函數,將屬性也初始化了,以下圖所示:函數
然而並不須要這樣作,咱們是想使用原型上的方法,屬性的繼承徹底能夠留待new Person('bob', 'JavaScript')
時經過Human.call(this, name)
來調用父類型的構造函數,這裏new Human()
等於事先多調用了一次Human
的構造函數,影響了代碼的效率。this
再者,即使父類型已經初始化了屬性,待未來建立子類型的對象,子類型對象的屬性也會屏蔽父類型已經初始化好的屬性,也就是說new Human()
初始化的屬性根本就沒什麼用處,以下圖所示: .net
p
上的colors
和name
會屏蔽掉原型上的colors
和name
, 那麼有沒有一種方案能夠實現只使用原型上的方法,而不用調用父類型的構造函數呢?方案是有的,這就是本篇文章要講的寄生組合式繼承。prototype
咱們能夠直觀地想一下,既然只是想使用原型上的方法,不調用構造函數,那麼直接將父類型的原型賦給子類型的原型不就能夠了嗎?像下面這樣:3d
//繼承方法 Person.prototype = Human.prototype;
表面上看是沒什麼問題,假如要用子類型對象調用sayName
方法時,對象上沒有,因而到原型去找,原型就是Human
的原型,因而找到了sayName
方法,成功調用。可是結合原型鏈和對象深淺複製的知識(能夠參考JavaScript繼承(一)——原型鏈和JavaScript中對象的淺複製和深複製),Human
自己就是對象,Human.prototype
實際上是Human
原型對象的引用,Person.prototype = Human.prototype
就是引用的複製,結果就是Person.prototype
和Human.prototype
指向同一個對象——Human
的原型對象,而原型對象的constructor
屬性是指向函數的,那麼如今咱們讓Human
原型對象的constructor
屬性指向誰呢?Human
仍是Person
?顯然原型鏈被打亂了。想起了哪吒的三頭六臂,要是再有一個constructor
屬性就行了。code
這時原型式繼承又派上用場了,以下所示:對象
//繼承方法 Person.prototype = object(Human.prototype); Person.prototype.constructor = Person;
咱們可使用原型式繼承建立Human
原型對象的一個子對象,這個子對象賦給Person.prototype
,改變這個子對象的constructor
爲Person
,這樣既能夠達到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
方法,這就是寄生組合式繼承。
寄生組合式繼承的高效率體如今它只調用了一次父類型的構造函數,而且所以避免了在子類型的原型上面建立沒必要要的、多餘的屬性。與此同時,原型鏈還能保持不變;所以,還可以正常使用instanceof
和isPrototypeOf()
。開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承模式。