夯基礎(chǔ)-手撕js繼承

提到JS繼承勾怒,你首先想到的什么婆排? 面試 繼承方式 優(yōu)缺點...声旺,js繼承作為曾經(jīng)的苦主,我看了忘段只,忘了看腮猖,看了又忘,OMG赞枕,都9012年了面試官還不放過我澈缺。

image

ok,開開玩笑炕婶,接下來言歸正傳姐赡,來聊聊js繼承這個經(jīng)典的話題。

JS的“類”

javascript不像java柠掂,php等傳統(tǒng)的OOP語言项滑,js本身并沒有類這個概念,那么它是怎么實現(xiàn)類的模擬呢涯贞?

  1. 構(gòu)造函數(shù)方式
  2. 原型方式
  3. 混合方式

構(gòu)造函數(shù)方式

Function Foo (name) {
    this.name = name
    this.like = function () {
        console.log(`like${this.name}`)
    }
}
let foo = new Foo('bibidong')

像這樣就是通過構(gòu)造函數(shù)的方式來定義類枪狂,其實和普通函數(shù)一樣,但為了和常規(guī)函數(shù)有個區(qū)分宋渔,一般把函數(shù)名首字母大寫州疾。

  • 缺點:無法共享類的方法。

原型方式

function Foo (name) {}
Foo.prototype.color = 'red'
Foo.prototype.queue = [1,2,3]
let foo1 = new Foo()
let foo2 = new Foo()

foo1.queue.push(4)
console.log(foo1)   // [1, 2, 3, 4]
console.log(foo2)   // [1, 2, 3, 4]

我們通過原型方式直接把屬性和方法定義在了構(gòu)造函數(shù)的原型對象上皇拣,實例可以共享這些屬性和方法严蓖,解決了構(gòu)造函數(shù)方式定義類的缺點。

  • 缺點:可以看到我們改變了foo1的數(shù)據(jù)氧急,結(jié)果foo2的queue屬性也變了颗胡,這便是原型方式最大的問題,引用類型的屬性會被其它實例修改态蒂。除此之外杭措,這種方式下也無法傳參。

混合方式

function Foo (name) {   // 屬性定義在構(gòu)造函數(shù)里面
    this.name = name
    this.color = 'red'
    this.queue = [1,2,3]
}
Foo.prototype.like = function () {  // 方法定義在原型上
    console.log(`like${this.name}`)
}
let foo1 = new Foo()
let foo2 = new Foo()

所謂混合模式钾恢,便是把上面兩種方式混合起來手素,我們在構(gòu)造函數(shù)里面定義屬性,在原型對象上定義要共享的方法瘩蚪,既能傳參泉懦,也避免了原型模式的問題。

小結(jié)一下:js類的能力是模擬出來的疹瘦,可以通過構(gòu)造函數(shù)方式崩哩,原型方式來定義,混合模式則聚合了前兩者的優(yōu)點。除此邓嘹,還有Object.create(), es6的class酣栈,都可以來創(chuàng)建對象,定義類汹押。

常見的繼承方式

一矿筝、原型鏈繼承

基于原型鏈查找的特點,我們將父類的實例作為子類的原型棚贾,這種繼承方式便是原型鏈繼承窖维。

function Parent () {
    this.color = 'red'
    this.queue = [1,2,3]
}
Parent.prototype.like = function () {
    console.log('')
}

function Child () { }
Child.prototype = new Parent()  // constructor指針變了 指向了Parent
Child.prototype.constructor = Child     // 手動修復(fù)

let child = new Child()

Child.prototype相當于是父類Parent的實例,父類Parent的實例屬性被掛到了子類的原型對象上面妙痹,拿color屬性舉個例子铸史,相當于就是這樣

Child.prototype.color = 'red'

這樣父類的實例屬性都被共享了,我們打印一下child怯伊,可以看到child沒有自己的實例屬性琳轿,它訪問的是它的原型對象。

image

我們創(chuàng)建兩個實例child1震贵,child2

let child1 = new Child()
let child2 = new Child()
child1.color = 'bulr'
console.log(child1)
console.log(child2)
image

我們修改了child1的color屬性利赋,child2沒有受到影響水评,并非是其它實例擁有獨立的color屬性猩系,而是因為這個color屬性直接添加到了child1上面,它原型上的color并沒有動中燥,所以其它實例不會受到影響從打印結(jié)果也可以清楚看到這一點寇甸。那如果我們修改的屬性是個引用類型呢?

child1.queue = [1,2,3,'我被修改了'] // 重新賦值
child1.like = function () {console.log('like方法被我修改了')}
console.log(child1)
console.log(child2)
image

我們重寫了引用類型的queue屬性和like方法疗涉,其實和修改color屬性是完全一樣的拿霉,它們都直接添加到了child1的實例屬性上。從打印結(jié)果能看到這兩個屬性已經(jīng)添加到了child1上了咱扣,而child2并不會受到影響绽淘,再來看下面這個。

child1.queue.push('add push')   // 這次沒有重新賦值
console.log(child1)
console.log(child2)
image

如果進行了重新賦值闹伪,會添加到到實例屬性上沪铭,和原型上到同名屬性便無關(guān)了,所以并不會影響到原型偏瓤。這次我們采用push方法杀怠,沒有開辟新空間,修改的就是原型厅克。child2的queue屬性變化了赔退,子類Child原型上的queue屬性被實例修改,這樣肯定就影響到了所有實例。

  • 缺點
    • 子類的實例會共享父類構(gòu)造函數(shù)引用類型的屬性
    • 創(chuàng)建子類實例的時候無法傳參

二硕旗、構(gòu)造函數(shù)式繼承

相當于拷貝父類的實例屬性給子類窗骑,增強了子類構(gòu)造函數(shù)的能力

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)    // 核心代碼
}

let child = new Child(1)
image

我們打印了一下child,可以看到子類擁有父類的實例屬性和方法漆枚,但是child的__proto__上面沒有父類的原型對象慧域。解決了原型鏈的兩個問題(子類實例的各個屬性相互獨立、還能傳參)

  • 缺點
    • 子類無法繼承父類原型上面的方法和屬性浪读。
    • 在構(gòu)造函數(shù)中定義的方法昔榴,每次創(chuàng)建實例都會再創(chuàng)建一遍。

三碘橘、組合繼承

人如其名互订,組合組合,一定把什么東西組合起來痘拆。沒錯仰禽,組合繼承便是把上面兩種繼承方式進行組合。

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 = new Parent()
Child.prototype.constructor = Child     // 修復(fù)constructor指針
let child = new Child('')

接下來我們做點什么纺蛆,看它組合后能不能把原型鏈繼承和構(gòu)造函數(shù)繼承的優(yōu)點發(fā)揚光大

let child1 = new Child('bibidong')
let child2 = new Child('huliena')
child1.queue.push('add push')
console.log(child1)
console.log(child2)

image

我們更新了child1的引用屬性吐葵,發(fā)現(xiàn)child2實例沒受到影響,原型上的like方法也在桥氏,不錯温峭,組合繼承確實將二者的優(yōu)點發(fā)揚光大了,解決了二者的缺點字支。組合模式下凤藏,通常在構(gòu)造函數(shù)上定義實例屬性,在原型對象上定義要共享的方法堕伪,通過原型鏈繼承方法讓子類繼承父類構(gòu)造函數(shù)原型上的方法揖庄,通過構(gòu)造函數(shù)繼承方法子類得以繼承構(gòu)造函數(shù)的實例屬性,是一種功能上較完美的繼承方式欠雌。

  • 缺點:父類構(gòu)造函數(shù)被調(diào)用了兩次蹄梢,第一次調(diào)用后,子類的原型上擁有了父類的實例屬性富俄,第二次call調(diào)用復(fù)制了一份父類的實例屬性作為子類Child的實例屬性禁炒,那么子類原型上的同名屬性就被覆蓋了。雖然被覆蓋了功能上沒什么大問題蛙酪,但這份多余的同名屬性一直存在子類原型上齐苛,如果我們刪除實例上的這個屬性,實際上還能訪問到桂塞,此時獲取到的是它原型上的屬性凹蜂。
Child.prototype = new Parent() // 第一次構(gòu)建原型鏈
Parent.call(this, name) // 第二次new操作符內(nèi)部通過call也執(zhí)行了一次父類構(gòu)造函數(shù)

四、原型式繼承

將一個對象作為基礎(chǔ),經(jīng)過處理得到一個新對象玛痊,這個新對象會將原來那個對象作為原型汰瘫,這種繼承方式便是原型式繼承,一句話總結(jié)就是將傳入的對象作為要創(chuàng)建的新對象的原型擂煞。

先寫下這個有處理能力的函數(shù)

function prodObject (obj) {
    function F (){
        
    }
    F.prototype = obj
    return new F()  // 返回一個實例對象
}

這也是Object.create()的實現(xiàn)原理混弥,所以用Object.create直接替換prodObject函數(shù)是ok的
let base = {
    color: 'red',
    queue: [1, 2, 3]
}
let child1 = prodObject(base)
let child2 = prodObject(base)
console.log(child1)
console.log(child2)
image

原型式繼承基于prototype,和原型鏈繼承類似对省,這種繼承方式下實例沒有自己的屬性值蝗拿,訪問到也是原型上的屬性。

  • 缺點:同原型鏈繼承

五蒿涎、寄生式繼承

原型式繼承的升級哀托,寄生繼承封裝了一個函數(shù),在內(nèi)部增強了原型式繼承產(chǎn)生的對象劳秋。

function greaterObject (obj) {
    let clone = prodObject(obj)
    clone.queue = [1, 2, 3]
    clone.like = function () {}
    return clone
}
let parent = {
    name: 'bibidong',
    color: ['red', 'bule', 'black']
}
let child = greaterObject(parent)
image

打印了一下child仓手,它的缺點也很明顯了,寄生式繼承增強了對象玻淑,卻也無法避免原型鏈繼承的問題嗽冒。

  • 缺點
    • 擁有原型鏈繼承的缺點
    • 除此,內(nèi)部的函數(shù)無法復(fù)用

六补履、寄生組合式繼承

大招來了添坊,寄生組合閃亮登場!

image

上面說到干像,組合繼承的問題在于會調(diào)用二次父類帅腌,造成子類原型上產(chǎn)生多余的同名屬性驰弄。Child.prototype = new Parent()麻汰,那這行代碼該怎么改造呢?

我們的目的是要讓父類的實例屬性不出現(xiàn)在子類原型上戚篙,如果讓Child.prototype = Parent.prototype五鲫,這樣不就能保證子類只掛載父類原型上的方法,實例屬性不就沒了嗎岔擂,代碼如下位喂,看起來好像是簡直不要太妙啊。

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 // 只改寫了這一行
Child.prototype.constructor = Child
let child = new Child('')

回過神突然發(fā)現(xiàn)改寫的那一行如果Child.prototype改變了乱灵,那豈不是直接影響到了父類塑崖,舉個栗子

Child.prototype.addByChild = function () {}
Parent.prototype.hasOwnProperty('addByChild')   // true

addByChild方法也被加到了父類的原型上,所以這種方法不夠優(yōu)雅痛倚。同樣還是那一行规婆,直接訪問到Parent.prototype存在問題,那我們可以產(chǎn)生一個以Parent.prototype作為原型的新對象,這不就是上面原型式繼承的處理函數(shù)prodObject

Child.prototype = Object.create(Parent.prototype) // 改為這樣

這樣就解決了所有問題抒蚜,我們怕改寫Child.prototype影響父類掘鄙,通過Object.create返回的實例對象,我們將Child.prototype間接指向Parent.prototype嗡髓,當再增加addByChild方法時操漠,屬性就和父類沒關(guān)系了。

寄生組合式繼承也被認為是最完美的繼承方式饿这,最推薦使用浊伙。

總結(jié)

js的繼承方式主要就這六種,es6的繼承是個語法糖长捧,本質(zhì)也是基于寄生組合吧黄。這六種繼承方式,其中原型鏈繼承和構(gòu)造函數(shù)繼承最為基礎(chǔ)和經(jīng)典唆姐,組合繼承聚合了它們二者的能力拗慨,但在某些情況下會造成錯誤。原型式繼承和原型鏈相似奉芦,寄生式繼承是在原型式繼承基礎(chǔ)上變化而來赵抢,它增強了原型式繼承的能力。最后的寄生組合繼承解決了組合繼承的問題声功,是一種最為理想的繼承方式烦却。


今天七夕,在線乞討先巴,不要女朋友只要贊其爵,溜了溜了~


image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伸蚯,隨后出現(xiàn)的幾起案子摩渺,更是在濱河造成了極大的恐慌,老刑警劉巖剂邮,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摇幻,死亡現(xiàn)場離奇詭異,居然都是意外死亡挥萌,警方通過查閱死者的電腦和手機绰姻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來引瀑,“玉大人狂芋,你說我怎么就攤上這事『┰裕” “怎么了帜矾?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵辆影,是天一觀的道長。 經(jīng)常有香客問我黍特,道長蛙讥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任灭衷,我火速辦了婚禮次慢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘翔曲。我一直安慰自己迫像,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布瞳遍。 她就那樣靜靜地躺著闻妓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掠械。 梳的紋絲不亂的頭發(fā)上由缆,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音猾蒂,去河邊找鬼均唉。 笑死,一個胖子當著我的面吹牛肚菠,可吹牛的內(nèi)容都是我干的舔箭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蚊逢,長吁一口氣:“原來是場噩夢啊……” “哼层扶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起烙荷,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤镜会,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后奢讨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稚叹,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年拿诸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塞茅。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡亩码,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出野瘦,到底是詐尸還是另有隱情描沟,我是刑警寧澤飒泻,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站吏廉,受9級特大地震影響泞遗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜席覆,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一史辙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧佩伤,春花似錦聊倔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至孤荣,卻和暖如春甸陌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盐股。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工邀层, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人遂庄。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓寥院,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涛目。 傳聞我的和親對象是個殘疾皇子秸谢,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359