JavaScript六大繼承方式

解析幾種繼承方式

爲何須要繼承?javascript

當項目愈來愈大,代碼愈來愈複雜,部分功能開始重複就得考慮代碼複用,在代碼複用的前提下把能抽象出來的屬性和方法變成一個類,這個就是所謂的抽象類,若是須要在這個抽象類的基礎上還須要進行功能拓展可是又要建立一個基於此類的抽象類就須要用到繼承了。java

如下繼承方法都是基於ES5實現,ES6有專門的繼承語法糖了暫不作討論app

常見的繼承有哪幾種?函數

  1. 類式繼承
  2. 構造函數繼承
  3. 組合繼承
  4. ...下次補上

1.類式繼承(原型鏈繼承)

顧名思義類式繼承就是直接經過類的方法來繼承,咱們都知道JS是一門基於原型的語言,他是沒有具體類的概念的,可是咱們能夠經過prototype來實現關於類的種種功能。因此簡而言之類式繼承就是把子類的prototype鏈掛載到實例化的父類對象上。this

function  Father()  {

this.name  =  'dad'

this.clothColor  =  ['red',  'blue',  'green']

}

  

Father.prototype.addColor =  function(color)  {

this.clothColor.push(color)

}

  

Father.prototype.sayHi  =  function()  {

console.log('Hi')

}

  

function  Son()  {

this.name  =  'son'

this.age  =  10
 this.clothColor = ['white']

}

  

Son.prototype  =  new  Father()

  

let  fathers  =  new  Father()

let  sons1  =  new  Son()

let  sons2  =  new  Son()

  

sons1.addColor('yellow')

  

console.log(fathers.clothColor)  //\["red", "blue", "green"\]

console.log(sons1.clothColor)  // \["red", "blue", "green", "yellow"\]

console.log(sons2.clothColor)  // \["red", "blue", "green", "yellow"\]

console.log(sons1.sayHi())  // Hi

類式繼承的優勢是實現了代碼的複用,每次實例化都不用再去執行類代碼,全部子類獲得了同樣的功能,可是缺點也很明顯,一個子類改變了父類引用類型數據,全部子類都會改變prototype

2.構造函數繼承

構造函數繼承說的簡單點就是藉助父類的構造函數來實現繼承code

function  Father()  {

this.name  =  'dad'

this.clothColor  =  ['red',  'blue',  'green']

}

  

Father.prototype.addColor  =  function(color)  {

this.clothColor.push(color)

}

  

Father.prototype.sayHi  =  function()  {

console.log('Hi')

}

  

function  Son()  {

Father.call(this)

// Father.prototype.constructor = Father

  

this.name  =  'son'

this.age  =  10

}

  
  

let  fathers  =  new  Father()

let  sons1  =  new  Son()

let  sons2  =  new  Son()

  

sons1.addColor('yellow')  // 報錯
  1. 構造函數繼承和類式繼承很類似,只不過是把Son.prototype = new Father()改爲了Father.call(this),可是其實沒有涉及到原型的繼承,只是經過call、apply改變了父類的this指向,把this綁定到了Son類上,因此Son類沒法獲得父類的方法。
  2. 使用call、apply會致使每次實例化都會去執行父類,不符合代碼複用思想,會形成大量內存堆積。
  3. 在每一個子類實例中,每一個方法都做爲了實例本身的方法,當需求改變,要改動其中的一個方法時,以前全部的實例,他們的該方法都不能及時做出更新。只有後面的實例才能訪問到新方法。

3. 組合繼承

把組合繼承放到第一第二種下面講,就是由於組合就是原型和構造函數繼承相組合來實現繼承,彌補了以上兩種繼承的缺點orm

function  Father()  {

this.name  =  'dad'

this.clothColor  =  ['red',  'blue',  'green']

console.log('調用次數')  //4

}

  

Father.prototype.addColor  =  function(color)  {

this.clothColor.push(color)

}

  

Father.prototype.sayHi  =  function()  {

console.log('Hi')

}

  

function  Son()  {

Father.call(this)

this.name  =  'son'

this.age  =  10

}

  

Son.prototype  =  new  Father()

  

let  fathers  =  new  Father()

let  sons1  =  new  Son()

let  sons2 =  new  Son()

  

sons1.addColor('yellow')

  

console.log(fathers.clothColor)  //\["red", "blue", "green"]

console.log(sons1.clothColor)  // \["red", "blue", "green", "yellow"\]

console.log(sons2.clothColor)  // \["red", "blue", "green"\]

console.log(sons1.sayHi())  // Hi
  1. 組合繼承結合了以上兩種繼承的優缺點,比較經常使用
  2. 可是組合繼承會使子類擁有兩套屬性,一個是經過子類點直接訪問,一個是在子類的原型鏈上,效率較低
  3. 每次子類實例化都會去調用父類的問題依然沒獲得解決,效率較低

4. 原型式繼承

基於已有對象建立新對象。若是上面三種繼承方式屬於一類,下面講的幾種,包括原型式繼承就是另外一類繼。原型是繼承沒有使用嚴格的構造函數,必須有一個對象能夠做爲另外一個對象的基礎,將源對象傳入建立封裝對象的函數,再修改目標對象對象

function  inheritObject(obj)  {

function  Func()  {}

Func.prototype  =  obj

return  new  Func()

}

  

let  man  =  {

name:  'Norman',

clothColor:  ['red',  'blue']

}

  

let  newMan  =  inheritObject(man)

let  secondMan  =  inheritObject(man)

  

newMan.clothColor.push('green')

  

console.log(newMan.clothColor)  //\["red", "blue", "green"\]

console.log(secondMan.clothColor)//\["red", "blue", "green"\]

原型式繼承的好處是基於已有對象建立新對象,同時還沒必要所以建立自定義類型,可是因爲用到了原型鏈因此子類在修改父類引用型數據時,數據會在全部子類共享繼承

5.寄生繼承

寄生繼承和原型式繼承相似,都是經過建立一個用於封裝繼承的方法來生成對象。可是這個方法裏面能夠增長一些新增的方法。這裏的建立對象用到了ES5的Object.create()

function  inheritObject(obj)  {

let  clone =  Object.create(obj)

clone.getName  =  function()  {  // 增長對象屬性

return  'hhhh'

}

return  clone

}

  

let  man  =  {

name:  'Norman',

clothColor:  ['red',  'blue']

}

  

let  mans_1  =  new  inheritObject(man)

  

console.log(mans_1.name)

console.log(mans_1.getName())

缺點是每次建立一個新的對象都回去調用inheritObject方法不利於複用。

function  inheritObject(obj)  {

function  Func()  {}

Func.prototype  =  obj

return  new  Func()

}

// 等價於Object.create()

按照非ES5寫法其實就是把傳入的對象封裝一遍實例化,再對這個實例化對象進行擴展,最後再把這個對象實例化獲得目標對象。能夠說是原型式繼承的再一次封裝。

6.寄生組合式繼承

寄生組合式繼承是目前來講最好的繼承方式,先說組合式繼承,他是比較好的繼承方法,可是他會致使對象的原型鏈和對象屬性上出現兩套相同的屬性,也就是父類構造函數會執行兩次。寄生組合式的繼承方法就是把子類對象上的屬性去掉 只保留原型鏈上的屬性。

寄生組合式繼承要理解要寫出來,最好先從寫一個組合繼承開始,再將寄生繼承的概念融入

function  Father()  {

this.name  =  'dad'

this.clothColor  =  ['red',  'blue']

}

  

Father.prototype.getWords  =  function()  {

return  'hahahah'

}

  

function  Mother()  {

this.baby  =  1

}

  

Mother.prototype.getBaby  =  function()  {

return  'baby'

}

  

function  Son()  {

Father.call(this)

Mother.call(this)  //多重繼承 忘記寫拿不到baby屬性

this.age  =  15

}

  

function  inheritObject(sonClass, FatherClass, MotherClass)  {

let  clone

if  (MotherClass)  {

let  minin  =  Object.assign(FatherClass.prototype, MotherClass.prototype)

clone  =  Object.create(minin)  //多重繼承

}  else  {

clone  =  Object.create(FatherClass.prototype)  //基於父原型建立新對象 這樣操做會丟失構造函數默認的constructor屬性

}

clone.constructor  =  sonClass  // 新對象的構造器和子類綁定

sonClass.prototype  =  clone  // 把新對象賦值給繼承目標子類的原型鏈

}

  

inheritObject(Son,  Father,  Mother)

  

let  son  \=  new  Son()

  

console.log(son.getWords())

console.log(son.getBaby())

inheritObject函數接收兩個參數:子類型構造函數和超類型構造函數。

建立父類型原型的副本。爲建立的副本添加constructor屬性,彌補因重寫原型而失去的默認的constructor屬性。將新建立的對象(即副本)賦值給子類型的原型。這種方法只調用了一次父類構造函數,instanceof 和isPrototypeOf()也能正常使用。

經過對inheritObject的改動能夠傳入多個父類參數,Son構造函數中添加其餘父類的this指向,再經過Object.assign()方法把多個父類的原型鏈合併爲一個對象進行拷貝,就能夠實現多重繼承

相關文章
相關標籤/搜索