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