重學(xué)JS 系列:聊聊繼承(推薦)
原型
繼承得靠原型來實(shí)現(xiàn),當(dāng)然原型不是這篇文章的重點(diǎn),我們來復(fù)習(xí)一下即可。
其實(shí)原型的概念很簡(jiǎn)單:
- 所有對(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)容就是這些了,完全沒有必要去看那些長(zhǎng)篇大論什么是原型的文章,初學(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è)父類很難描述清楚所有場(chǎng)景,這就導(dǎo)致我們可能又需要新增幾個(gè)不同的父類去描述更多的場(chǎng)景。隨著不斷的擴(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)文章
es7學(xué)習(xí)教程之Decorators(修飾器)詳解
這篇文章主要給大家介紹了關(guān)于es7中Decorators(修飾器)的相關(guān)資料,文中通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)吧。2017-07-07
向?qū)Э梢宰屇愕木W(wǎng)站用戶快速上手使用你的web應(yīng)用,提高網(wǎng)站的吸引力。向?qū)б话惴譃楹脦讉€(gè)步驟,每個(gè)步驟收集一些數(shù)據(jù),并且支持退回功能,所有步驟完成后可以得到每一步的收集結(jié)果。這里給大家展示一種比較通用,靈活且簡(jiǎn)單的向?qū)Э蚣堋?/div> 2011-08-08
使用堆實(shí)現(xiàn)Top K算法(JS實(shí)現(xiàn))
這篇文章主要介紹了使用堆實(shí)現(xiàn)Top K算法,即JS實(shí)現(xiàn),文中詳細(xì)介紹了Top K算法,感興趣的小伙伴們可以參考一下2015-12-12
JavaScript利用normalizr實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換
當(dāng)我們需要進(jìn)行數(shù)據(jù)轉(zhuǎn)換以便拆分和維護(hù)時(shí),可以使用redux作者 Dan Abramov 編寫的normalizr來處理數(shù)據(jù),本文將為大家詳細(xì)講講其用法,感興趣的可以了解一下2022-07-07
Javascript 模擬mvc實(shí)現(xiàn)點(diǎn)餐程序案例詳解
這篇文章主要介紹了Javascript 模擬mvc實(shí)現(xiàn)點(diǎn)餐程序案例詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
js實(shí)現(xiàn)select跳轉(zhuǎn)菜單新窗口效果代碼分享
這篇文章主要介紹了js實(shí)現(xiàn)select跳轉(zhuǎn)菜單新窗口效果代碼分享,實(shí)現(xiàn)很簡(jiǎn)單,推薦給大家,有需要的小伙伴可以參考下2015-08-08
鍵盤上一張下一張兼容IE/google/firefox等瀏覽器
鍵盤上一張下一張的效果想必大家都有見到過吧,本文為大家介紹的這個(gè)兼容IE,google,firefox等主流瀏覽器2014-01-01
extjs中form與grid交互數(shù)據(jù)(record)的方法
這篇文章介紹了extjs中form與grid交互數(shù)據(jù)(record)的方法,有需要的朋友可以參考一下2013-08-08最新評(píng)論

