重學(xué)JS 系列:聊聊繼承(推薦)
原型
繼承得靠原型來實現(xiàn),當(dāng)然原型不是這篇文章的重點,我們來復(fù)習(xí)一下即可。
其實原型的概念很簡單:
- 所有對象都有一個屬性 __proto__ 指向一個對象,也就是原型
- 每個對象的原型都可以通過 constructor 找到構(gòu)造函數(shù),構(gòu)造函數(shù)也可以通過 prototype 找到原型
- 所有函數(shù)都可以通過 __proto__ 找到 Function 對象
- 所有對象都可以通過 __proto__ 找到 Object 對象
- 對象之間通過 __proto__ 連接起來,這樣稱之為原型鏈。當(dāng)前對象上不存在的屬性可以通過原型鏈一層層往上查找,直到頂層 Object 對象
其實原型中最重要的內(nèi)容就是這些了,完全沒有必要去看那些長篇大論什么是原型的文章,初學(xué)者會越看越迷糊。
當(dāng)然如果你想了解更多原型的深入內(nèi)容,可以閱讀我 之前寫的文章。
ES5 實現(xiàn)繼承
ES5 實現(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)點在于構(gòu)造函數(shù)可以傳參,不會與父類引用屬性共享,可以復(fù)用父類的函數(shù),但是也存在一個缺點就是在繼承父類函數(shù)的時候調(diào)用了父類構(gòu)造函數(shù),導(dǎo)致子類的原型上多了不需要的父類屬性,存在內(nèi)存上的浪費。

寄生組合繼承
這種繼承方式對組合繼承進(jìn)行了優(yōu)化,組合繼承缺點在于繼承父類函數(shù)時調(diào)用了構(gòu)造函數(shù),我們只需要優(yōu)化掉這點就行了。
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
以上繼承實現(xiàn)的核心就是將父類的原型賦值給了子類,并且將構(gòu)造函數(shù)設(shè)置為子類,這樣既解決了無用的父類屬性問題,還能正確的找到子類的構(gòu)造函數(shù)。

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

