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)題,是一種最為理想的繼承方式。