js常見(jiàn)的繼承方式

1.原型鏈繼承

基于原型鏈查找的特點(diǎn),我們將父類(lèi)的實(shí)例作為子類(lèi)的原型,這種繼承方式便是原型鏈繼承。

function Parent() {
  this.color = 'red'
  this.queue = [1,2,3]
}
Parent.prototype.like = function () {
  console.log('初始狀態(tài)')
}
function Child () {}
Child.prototype = new Parent() // constructor指針變了 指向了Parent
Child.prototype.constructor = Child // 手動(dòng)修復(fù)
let child = new Child()

Child.prototype相當(dāng)于是父類(lèi)Parent的實(shí)例,父類(lèi)Parent的實(shí)例屬性被掛到了子類(lèi)的原型對(duì)象上面,拿color屬性舉個(gè)例子,相當(dāng)于就是這樣

Child.prototype.color = 'red'

這樣父類(lèi)的實(shí)例屬性都被共享了,我們打印一下child,可以看到child沒(méi)有自己的實(shí)例屬性,它訪問(wèn)的是它的原型對(duì)象。



我們創(chuàng)建兩個(gè)實(shí)例child1,child2

let child1 = new Child()
let child2 = new Child()
child1.color = 'blue'
console.log(child1,child2)

我們修改了child1的color屬性,child2沒(méi)有受到影響,并非是其它實(shí)例擁有獨(dú)立的color屬性,而是因?yàn)檫@個(gè)color屬性直接添加到了child1上面,它原型上的color并沒(méi)有動(dòng),所以其它實(shí)例不會(huì)受到影響從打印結(jié)果也可以清楚看到這一點(diǎn)。那如果我們修改的屬性是個(gè)引用類(lèi)型呢?

let child1 = new Child()
let child2 = new Child()
child1.queue = [1,2,3,4]
child1.like = function () {
  console.log('我被修改了')
}
console.log(child1,child2)

我們重寫(xiě)了引用類(lèi)型的queue屬性和like方法,其實(shí)和修改color屬性是完全一樣的,它們都直接添加到了child1的實(shí)例屬性上。從打印結(jié)果能看到這兩個(gè)屬性已經(jīng)添加到了child1上了,而child2并不會(huì)受到影響,再來(lái)看下面這個(gè)。

let child1 = new Child()
let child2 = new Child()
child1.queue.push(4)  // 這次沒(méi)有重新賦值
console.log(child1,child2)

如果進(jìn)行了重新賦值,會(huì)添加到到實(shí)例屬性上,和原型上到同名屬性便無(wú)關(guān)了,所以并不會(huì)影響到原型。這次我們采用push方法,沒(méi)有開(kāi)辟新空間,修改的就是原型。child2的queue屬性變化了,子類(lèi)Child原型上的queue屬性被實(shí)例修改,這樣肯定就影響到了所有實(shí)例。
缺點(diǎn):
1.子類(lèi)的實(shí)例會(huì)共享父類(lèi)構(gòu)造函數(shù)引用類(lèi)型的屬性
2.創(chuàng)建子類(lèi)實(shí)例的時(shí)候無(wú)法傳參

2.構(gòu)造函數(shù)式繼承

相當(dāng)于拷貝父類(lèi)的實(shí)例屬性給子類(lèi),增強(qiáng)子類(lèi)構(gòu)造函數(shù)的能力

function Parent (name) {
  this.name = name
  this.queue = [1,2,3]
}
Parent.prototype.like = function () {
  console.log(`I like ${this.name}`)
}
function Child (name) {
  Parent.call(this, name)
}
let child = new Child('xxx')
console.log(child)


我們打印了一下child,可以看到子類(lèi)擁有父類(lèi)的實(shí)例屬性和方法,但是child的proto上面沒(méi)有父類(lèi)的原型對(duì)象。解決了原型鏈的兩個(gè)問(wèn)題(子類(lèi)實(shí)例的各個(gè)屬性相互獨(dú)立、還能傳參)
缺點(diǎn):
1.子類(lèi)無(wú)法繼承父類(lèi)原型上面的方法和屬性。
2.在構(gòu)造函數(shù)中定義的方法,每次創(chuàng)建實(shí)例都會(huì)再創(chuàng)建一遍。

3.組合繼承

組合繼承便是把上面兩種繼承方式進(jìn)行組合。

function Parent(name) {
  this.name = name
  this.queue = [1,2,3]
}
Parent.prototype.like = function () {
  console.log(`I like ${this.name}`)
}
function Child(name) {
  Parent.call(this, name)  //通過(guò)構(gòu)造函數(shù)繼承方法子類(lèi)得以繼承構(gòu)造函數(shù)的實(shí)例屬性
}
Child.prototype = new Parent()  //通過(guò)原型鏈繼承方法讓子類(lèi)繼承父類(lèi)構(gòu)造函數(shù)原型上的方法
Child.prototype.constructor = Child
let child1 = new Child('xxx')
let child2 = new Child('ooo')
child1.queue.push(4)
console.log(child1,child2)

我們更新了child1的引用屬性,發(fā)現(xiàn)child2實(shí)例沒(méi)受到影響,原型上的like方法也在,不錯(cuò),組合繼承確實(shí)將二者的優(yōu)點(diǎn)發(fā)揚(yáng)光大了,解決了二者的缺點(diǎn)。組合模式下,通常在構(gòu)造函數(shù)上定義實(shí)例屬性,在原型對(duì)象上定義要共享的方法,通過(guò)原型鏈繼承方法讓子類(lèi)繼承父類(lèi)構(gòu)造函數(shù)原型上的方法,通過(guò)構(gòu)造函數(shù)繼承方法子類(lèi)得以繼承構(gòu)造函數(shù)的實(shí)例屬性,是一種功能上較完美的繼承方式。

缺點(diǎn):父類(lèi)構(gòu)造函數(shù)被調(diào)用了兩次,第一次調(diào)用后,子類(lèi)的原型上擁有了父類(lèi)的實(shí)例屬性,第二次call調(diào)用復(fù)制了一份父類(lèi)的實(shí)例屬性作為子類(lèi)Child的實(shí)例屬性,那么子類(lèi)原型上的同名屬性就被覆蓋了。雖然被覆蓋了功能上沒(méi)什么大問(wèn)題,但這份多余的同名屬性一直存在子類(lèi)原型上,如果我們刪除實(shí)例上的這個(gè)屬性,實(shí)際上還能訪問(wèn)到,此時(shí)獲取到的是它原型上的屬性。

Child.prototype = new Parent() // 第一次構(gòu)建原型鏈
Parent.call(this, name) // 第二次new操作符內(nèi)部通過(guò)call也執(zhí)行了一次父類(lèi)構(gòu)造函數(shù)

4.原型式繼承

將一個(gè)對(duì)象作為基礎(chǔ),經(jīng)過(guò)處理得到一個(gè)新對(duì)象,這個(gè)新對(duì)象會(huì)將原來(lái)那個(gè)對(duì)象作為原型,這種繼承方式便是原型式繼承,一句話總結(jié)就是將傳入的對(duì)象作為要?jiǎng)?chuàng)建的新對(duì)象的原型。

function prodObject (obj) {
  function F () {}
  F.prototype = obj
  return new F()
}  // Object.create()的實(shí)現(xiàn)原理
let base = {
  name: 'xxx',
  queue: [1,2,3]
}
let child1 = prodObject(base)
let child2 = prodObject(base)
console.log(child1, child2)

原型式繼承基于prototype,和原型鏈繼承類(lèi)似,這種繼承方式下實(shí)例沒(méi)有自己的屬性值,訪問(wèn)到也是原型上的屬性。
缺點(diǎn):同原型鏈繼承

5.寄生式繼承

原型式繼承的升級(jí),寄生繼承封裝了一個(gè)函數(shù),在內(nèi)部增強(qiáng)了原型式繼承產(chǎn)生的對(duì)象。

function prodObject(obj) {
  function F () {
  }
  F.prototype = obj
  return new F()
}
function greaterObject(obj) {
  let clone = prodObject(obj)
  clone.queue = [1,2,3]
  clone.like = function() {}
  return clone
}
let parent = {
  name:'xxx',
  color: ['red', 'blue']
}
let child = greaterObject(parent)
console.log(child)

它的缺點(diǎn)也很明顯了,寄生式繼承增強(qiáng)了對(duì)象,卻也無(wú)法避免原型鏈繼承的問(wèn)題。
缺點(diǎn):
1.擁有原型鏈繼承的缺點(diǎn)
2.除此,內(nèi)部的函數(shù)無(wú)法復(fù)用

6.寄生組合式繼承

上面說(shuō)到,組合繼承的問(wèn)題在于會(huì)調(diào)用二次父類(lèi),造成子類(lèi)原型上產(chǎn)生多余的同名屬性。Child.prototype = new Parent(),那這行代碼該怎么改造呢?
我們的目的是要讓父類(lèi)的實(shí)例屬性不出現(xiàn)在子類(lèi)原型上,如果讓Child.prototype = Parent.prototype,這樣不就能保證子類(lèi)只掛載父類(lèi)原型上的方法,實(shí)例屬性不就沒(méi)了嗎,代碼如下,看起來(lái)好像是簡(jiǎn)直不要太妙啊。

function Parent (name) {
    this.name = name
    this.queue = [1,2,3]
}
Parent.prototype.like = function () {
    console.log(`like${this.name}`)
}

function Child (name) {
    Parent.call(this, name)
}

Child.prototype = Parent.prototype // 只改寫(xiě)了這一行
Child.prototype.constructor = Child
let child = new Child('')
console.log(child)

回過(guò)神突然發(fā)現(xiàn)改寫(xiě)的那一行如果Child.prototype改變了,那豈不是直接影響到了父類(lèi),舉個(gè)栗子

function Parent (name) {
    this.name = name
    this.queue = [1,2,3]
}
Parent.prototype.like = function () {
    console.log(`like${this.name}`)
}

function Child (name) {
    Parent.call(this, name)
}

Child.prototype = Parent.prototype // 只改寫(xiě)了這一行
Child.prototype.constructor = Child
Child.prototype.addByChild = function () {}
Parent.prototype.hasOwnProperty('addByChild')
let child = new Child('')
console.log(child, new Parent())

addByChild方法也被加到了父類(lèi)的原型上,所以這種方法不夠優(yōu)雅。同樣還是那一行,直接訪問(wèn)到Parent.prototype存在問(wèn)題,那我們可以產(chǎn)生一個(gè)以Parent.prototype作為原型的新對(duì)象,這不就是上面原型式繼承的處理函數(shù)prodObject嗎

Child.prototype = Object.create(Parent.prototype) // 改為這樣

這樣就解決了所有問(wèn)題,我們怕改寫(xiě)Child.prototype影響父類(lèi),通過(guò)Object.create返回的實(shí)例對(duì)象,我們將Child.prototype間接指向Parent.prototype,當(dāng)再增加addByChild方法時(shí),屬性就和父類(lèi)沒(méi)關(guān)系了。
寄生組合式繼承也被認(rèn)為是最完美的繼承方式,最推薦使用。

總結(jié)

js的繼承方式主要就這六種,es6的繼承是個(gè)語(yǔ)法糖,本質(zhì)也是基于寄生組合。這六種繼承方式,其中原型鏈繼承和構(gòu)造函數(shù)繼承最為基礎(chǔ)和經(jīng)典,組合繼承聚合了它們二者的能力,但在某些情況下會(huì)造成錯(cuò)誤。原型式繼承和原型鏈相似,寄生式繼承是在原型式繼承基礎(chǔ)上變化而來(lái),它增強(qiáng)了原型式繼承的能力。最后的寄生組合繼承解決了組合繼承的問(wèn)題,是一種最為理想的繼承方式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容