重學(xué)JS 系列:聊聊繼承(推薦)
原型
繼承得靠原型來(lái)實(shí)現(xiàn),當(dāng)然原型不是這篇文章的重點(diǎn),我們來(lái)復(fù)習(xí)一下即可。
其實(shí)原型的概念很簡(jiǎn)單:
- 所有對(duì)象都有一個(gè)屬性 __proto__ 指向一個(gè)對(duì)象,也就是原型
- 每個(gè)對(duì)象的原型都可以通過(guò) constructor 找到構(gòu)造函數(shù),構(gòu)造函數(shù)也可以通過(guò) prototype 找到原型
- 所有函數(shù)都可以通過(guò) __proto__ 找到 Function 對(duì)象
- 所有對(duì)象都可以通過(guò) __proto__ 找到 Object 對(duì)象
- 對(duì)象之間通過(guò) __proto__ 連接起來(lái),這樣稱之為原型鏈。當(dāng)前對(duì)象上不存在的屬性可以通過(guò)原型鏈一層層往上查找,直到頂層 Object 對(duì)象
其實(shí)原型中最重要的內(nèi)容就是這些了,完全沒(méi)有必要去看那些長(zhǎng)篇大論什么是原型的文章,初學(xué)者會(huì)越看越迷糊。
當(dāng)然如果你想了解更多原型的深入內(nèi)容,可以閱讀我 之前寫的文章。
ES5 實(shí)現(xiàn)繼承
ES5 實(shí)現(xiàn)繼承總的來(lái)說(shuō)就兩種辦法,之前寫過(guò)這方面的內(nèi)容,就直接復(fù)制來(lái)用了。
總的來(lái)說(shuō)這部分的內(nèi)容我覺(jué)得在當(dāng)下更多的是為了應(yīng)付面試吧。
組合繼承
組合繼承是最常用的繼承方式,
function Parent(value) {
this.val = value
}
Parent.prototype.getValue = function() {
console.log(this.val)
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = new Parent()
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
以上繼承的方式核心是在子類的構(gòu)造函數(shù)中通過(guò) Parent.call(this) 繼承父類的屬性,然后改變子類的原型為 new Parent() 來(lái)繼承父類的函數(shù)。
這種繼承方式優(yōu)點(diǎn)在于構(gòu)造函數(shù)可以傳參,不會(huì)與父類引用屬性共享,可以復(fù)用父類的函數(shù),但是也存在一個(gè)缺點(diǎn)就是在繼承父類函數(shù)的時(shí)候調(diào)用了父類構(gòu)造函數(shù),導(dǎo)致子類的原型上多了不需要的父類屬性,存在內(nèi)存上的浪費(fèi)。

寄生組合繼承
這種繼承方式對(duì)組合繼承進(jìn)行了優(yōu)化,組合繼承缺點(diǎn)在于繼承父類函數(shù)時(shí)調(diào)用了構(gòu)造函數(shù),我們只需要優(yōu)化掉這點(diǎn)就行了。
function Parent(value) {
this.val = value
}
Parent.prototype.getValue = function() {
console.log(this.val)
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true
}
})
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
以上繼承實(shí)現(xiàn)的核心就是將父類的原型賦值給了子類,并且將構(gòu)造函數(shù)設(shè)置為子類,這樣既解決了無(wú)用的父類屬性問(wèn)題,還能正確的找到子類的構(gòu)造函數(shù)。

Babel 如何編譯 ES6 Class 的
為什么在前文說(shuō) ES5 實(shí)現(xiàn)繼承更多的是應(yīng)付面試呢,因?yàn)槲覀儸F(xiàn)在可以直接使用 class 來(lái)實(shí)現(xiàn)繼承。
但是 class 畢竟是 ES6 的東西,為了能更好地兼容瀏覽器,我們通常都會(huì)通過(guò) Babel 去編譯 ES6 的代碼。接下來(lái)我們就來(lái)了解下通過(guò) Babel 編譯后的代碼是怎么樣的。
function _possibleConstructorReturn (self, call) {
// ...
return call && (typeof call === 'object' || typeof call === 'function') ? call : self;
}
function _inherits (subClass, superClass) {
// ...
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
var Parent = function Parent () {
// 驗(yàn)證是否是 Parent 構(gòu)造出來(lái)的 this
_classCallCheck(this, Parent);
};
var Child = (function (_Parent) {
_inherits(Child, _Parent);
function Child () {
_classCallCheck(this, Child);
return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
}
return Child;
}(Parent));
以上代碼就是編譯出來(lái)的部分代碼,隱去了一些非核心代碼,我們先來(lái)閱讀 _inherits 函數(shù)。
設(shè)置子類原型部分的代碼其實(shí)和寄生組合繼承是一模一樣的,側(cè)面也說(shuō)明了這種實(shí)現(xiàn)方式是最好的。但是這部分的代碼多了一句 Object.setPrototypeOf(subClass, superClass),其實(shí)這句代碼的作用是為了繼承到父類的靜態(tài)方法,之前我們實(shí)現(xiàn)的兩種繼承方法都是沒(méi)有這個(gè)功能的。
然后 Child 構(gòu)造函數(shù)這塊的代碼也基本和之前的實(shí)現(xiàn)方式類似。所以總的來(lái)說(shuō) Babel 實(shí)現(xiàn)繼承的方式還是寄生組合繼承,無(wú)非多實(shí)現(xiàn)了一步繼承父類的靜態(tài)方法。
繼承存在的問(wèn)題
講了這么些如何實(shí)現(xiàn)繼承,現(xiàn)在我們來(lái)考慮下繼承是否是一個(gè)好的選擇?
總的來(lái)說(shuō),我個(gè)人不怎么喜歡繼承,原因呢就一個(gè)個(gè)來(lái)說(shuō)。
我們先看代碼。假如說(shuō)我們現(xiàn)在要描述幾輛不同品牌的車,車必然是一個(gè)父類,然后各個(gè)品牌的車都分別是一個(gè)子類。
class Car {
constructor (brand) {
this.brand = brand
}
wheel () {
return '4 個(gè)輪子'
}
drvie () {
return '車可以開駕駛'
}
addOil () {
return '車可以加油'
}
}
Class OtherCar extends Car {}
這部分代碼在當(dāng)下看著沒(méi)啥毛病,實(shí)現(xiàn)了車的幾個(gè)基本功能,我們也可以通過(guò)子類去擴(kuò)展出各種車。
但是現(xiàn)在出現(xiàn)了新能源車,新能源車是不需要加油的。當(dāng)然除了加油這個(gè)功能不需要,其他幾個(gè)車的基本功能還是需要的。
如果新能源車直接繼承車這個(gè)父類的話,就出現(xiàn)了第一個(gè)問(wèn)題 ,大猩猩與香蕉問(wèn)題。這個(gè)問(wèn)題的意思是我們現(xiàn)在只需要一根香蕉,但是卻得到了握著香蕉的大猩猩,大猩猩其實(shí)我們是不需要的,但是父類還是強(qiáng)塞給了子類。繼承雖然可以重寫父類的方法,但是并不能選擇需要繼承什么東西。
另外單個(gè)父類很難描述清楚所有場(chǎng)景,這就導(dǎo)致我們可能又需要新增幾個(gè)不同的父類去描述更多的場(chǎng)景。隨著不斷的擴(kuò)展,代碼勢(shì)必會(huì)存在重復(fù),這也是繼承存在的問(wèn)題之一。
除了以上兩個(gè)問(wèn)題,繼承還存在強(qiáng)耦合的情況,不管怎么樣子類都會(huì)和它的父類耦合在一起。
既然出現(xiàn)了強(qiáng)耦合,那么這個(gè)架構(gòu)必定是脆弱的。一旦我們的父類設(shè)計(jì)的有問(wèn)題,就會(huì)對(duì)維護(hù)造成很大的影響。因?yàn)樗械淖宇惗己透割愸詈显谝黄鹆?,假如更改父類中的任何東西,都可能會(huì)導(dǎo)致需要更改所有的子類。
如何解決繼承的問(wèn)題
繼承更多的是去描述一個(gè)東西是什么,描述的不好就會(huì)出現(xiàn)各種各樣的問(wèn)題,那么我們是否有辦法去解決這些問(wèn)題呢?答案是組合。
什么是組合呢?你可以把這個(gè)概念想成是,你擁有各種各樣的零件,可以通過(guò)這些零件去造出各種各樣的產(chǎn)品,組合更多的是去描述一個(gè)東西能干什么。
現(xiàn)在我們把之前那個(gè)車的案例通過(guò)組合的方式來(lái)實(shí)現(xiàn)。
function wheel() {
return "4 個(gè)輪子";
}
function drvie() {
return "車可以開駕駛";
}
function addOil() {
return "車可以加油";
}
// 油車
const car = compose(wheel, drvie, addOil)
// 新能源車
const energyCar = compose(wheel, drive)
從上述偽代碼中想必你也發(fā)現(xiàn)了組合比繼承好的地方。無(wú)論你想描述任何東西,都可以通過(guò)幾個(gè)函數(shù)組合起來(lái)的方式去實(shí)現(xiàn)。代碼很干凈,也很利于復(fù)用。
最后
其實(shí)這篇文章的主旨還是后面兩小節(jié)的內(nèi)容,如果你還有什么疑問(wèn)歡迎在評(píng)論區(qū)與我互動(dòng)。
我所有的系列文章都會(huì)在我的 Github 中最先更新,有興趣的可以關(guān)注下。今年主要會(huì)著重寫以下三個(gè)專欄
- 重學(xué) JS
- React 進(jìn)階
- 重寫組件
以上所述是小編給大家介紹的JS繼承詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
uni-app自定義導(dǎo)航欄右側(cè)做增加按鈕并跳轉(zhuǎn)鏈接功能
這篇文章主要介紹了uni-app自定義導(dǎo)航欄右側(cè)做增加按鈕并跳轉(zhuǎn)鏈接,本文通過(guò)實(shí)例代碼給大家分享實(shí)現(xiàn)思路,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
bootstrap使用validate實(shí)現(xiàn)簡(jiǎn)單校驗(yàn)功能
這篇文章主要為大家詳細(xì)介紹了bootstrap使用validate實(shí)現(xiàn)簡(jiǎn)單校驗(yàn)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
JS重寫Date函數(shù)以及兼容IOS系統(tǒng)
這篇文章主要介紹了JS重寫Date函數(shù)以及兼容IOS系統(tǒng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
JavaScript實(shí)現(xiàn)鼠標(biāo)經(jīng)過(guò)顯示下拉框
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)鼠標(biāo)經(jīng)過(guò)顯示下拉框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04
Taro小程序自定義頂部導(dǎo)航欄功能的實(shí)現(xiàn)
這篇文章主要介紹了Taro小程序自定義頂部導(dǎo)航欄功能的實(shí)現(xiàn),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
一文詳解JS?類型轉(zhuǎn)換方法以及如何避免隱式轉(zhuǎn)換
這篇文章主要為大家介紹了JS?類型轉(zhuǎn)換方法以及如何避免隱式轉(zhuǎn)換示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
用JavaScript 判斷用戶使用的是 IE6 還是 IE7
判斷IE瀏覽器的腳本,方便根據(jù)瀏覽器不懂,支持不同的代碼的分別調(diào)用。2008-01-01
Next.js解決axios獲取真實(shí)ip問(wèn)題方法分析
這篇文章主要介紹了Next.js解決axios獲取真實(shí)ip問(wèn)題方法分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
javascript實(shí)現(xiàn)簡(jiǎn)單的二級(jí)聯(lián)動(dòng)
這篇文章主要介紹了javascript實(shí)現(xiàn)簡(jiǎn)單的二級(jí)聯(lián)動(dòng),非常的實(shí)用,需要的朋友可以參考下2015-03-03

