從 Prototype 開(kāi)始說(shuō)起(上)—— 圖解 ES5 繼承相關(guān)

Prototype__proto__

我們先寫下一行代碼:

function Parent {}

當(dāng)我們寫下這簡(jiǎn)單的一行代碼時(shí)亏拉,實(shí)際上發(fā)生了兩件事情

  • 創(chuàng)建了一個(gè)構(gòu)造函數(shù) Parent
  • 創(chuàng)建了一個(gè)原型對(duì)象 prototype

如下圖:

QQ20191116-200718@2x.png

構(gòu)造函數(shù) Parent 中 有一個(gè) prototype 的屬性指向 Parent 的 原型對(duì)象 prototype
原型對(duì)象 prototype 則有一個(gè) constructor 的屬性 指向回 構(gòu)造函數(shù) Parent

緊接著娇豫,我們又寫下一行代碼:

var parent = new Parent()

此時(shí)圆米,圖片上多出一個(gè)新成員

QQ20191116-201047@2x.png

注意到圖中的 Parent 的實(shí)例 parent 里悦荒,有一個(gè)[[prototype]],為什么這里不是 __proto__呢县好?

其實(shí)构哺,這里的 [[prototype]] 表示一種標(biāo)準(zhǔn)規(guī)范內(nèi)置屬性,被一些瀏覽器自己通過(guò)__proto__實(shí)現(xiàn)了熬芜,對(duì)于 Chrome 的實(shí)現(xiàn)來(lái)說(shuō)莲镣,這個(gè) __proto__ 也并不存在于 實(shí)例 parent中,而是 Object.prototype 的一個(gè) 存取描述符涎拉,以下代碼可以證明:

parent.hasOwnProperty('__proto__') // false

Object.prototype.hasOwnProperty('__proto__') // true

Object.getOwnPropertyDescriptor(Object.prototype, '__proto__')
/**
 * {
 *    configurable: true,
 *    enumerable: false,
 *    get: f __proto__()
 *    set: f __proto__()
 * }
 */

我們之所以能通過(guò) parent.__proto__ 訪問(wèn)到瑞侮,是因?yàn)橥ㄟ^(guò)原型鏈訪問(wèn)到了 Object.prototype 上的 __proto__ 存取描述符。

ES5 的 6 種繼承

以下內(nèi)容更像是《JavaScript高級(jí)程序設(shè)計(jì)》的筆記鼓拧,主要提煉出每個(gè)繼承的特點(diǎn)以及例圖半火。

原型鏈繼承

function Parent() {}
function Child() {}

var parent = new Parent()
Child.prototype = parent

var child = new Child()

此時(shí),根據(jù)第一部分所描述的細(xì)節(jié)季俩,我們很快可以畫出這幾行代碼所做的事情:

QQ20191116-204120@2x.png

這樣 child 就可以通過(guò)原型鏈繼承的方式訪問(wèn)到 parent 以及 Parent.prototype 上的屬性和方法了慈缔。 這種方式的特點(diǎn)是:

  • 引用類型的屬性為所有實(shí)例共享
  • 無(wú)法向父類構(gòu)造函數(shù)傳值

借用構(gòu)造函數(shù)繼承(經(jīng)典繼承)

function Parent(name){
    this.name = name
}
function Child(name){
    Parent.call(this, name)
}

var child1 = new Child('child1')
var child2 = new Child('child2')

可以看到,這種方式和 原型 沒(méi)有任何關(guān)系种玛,所以畫出的圖也很純粹:

QQ20191116-210140@2x.png

這種方式的特點(diǎn)是:

  • 每個(gè)實(shí)例上的屬性都是獨(dú)立的
  • 可以向父類構(gòu)造函數(shù)傳參
  • 每次創(chuàng)建實(shí)例都會(huì)創(chuàng)建一遍方法

組合繼承

顧名思義藐鹤,就是講上述兩種繼承方式有機(jī)結(jié)合,通過(guò)將方法定義在 prototype 中赂韵,屬性通過(guò)借用構(gòu)造函數(shù)繼承的方式實(shí)現(xiàn)繼承娱节。

function Parent(name) {
    this.name = name
}

Parent.prototype.talk = function () {}

function Child(name) {
    Parent.call(this, name)
}

var parent = new Parent('parent')
Child.prototype = parent
Child.prototype.constructor = Child

var child = new Child('child')

此時(shí),關(guān)系圖有了一些變化:

QQ20191117-202056.png

我們可以從圖中看到祭示,實(shí)例 child 和 實(shí)例 parent 各自擁有獨(dú)立的 namne肄满,但是共享 Parent.prototype 中的 talk() 方法。這種方式的特點(diǎn)是:

  • 擁有以上兩種方式的優(yōu)點(diǎn)
  • 執(zhí)行了兩次 父類構(gòu)造函數(shù) Parent

原型式繼承

function createObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

function Parent() {}

var parent = new Parent()

var child = object(parent)

這里先創(chuàng)建了一個(gè) createObject 函數(shù),其實(shí)就是 ES5 Object.create 的模擬實(shí)現(xiàn)稠歉,將傳入的對(duì)象作為創(chuàng)建的對(duì)象的原型掰担。

QQ20191116-212737@2x.png

原型鏈繼承 對(duì)比一下,我們發(fā)現(xiàn)其實(shí)是一樣的怒炸,除了可以不用創(chuàng)建一個(gè)自定義構(gòu)造函數(shù) Child带饱。所以特點(diǎn)和 原型鏈繼承 相同:

  • 引用類型的屬性為所有實(shí)例共享
  • 無(wú)法向父類構(gòu)造函數(shù)傳值

寄生式繼承

原型式繼承 的基礎(chǔ)上,創(chuàng)建一個(gè)僅用于封裝繼承過(guò)程的函數(shù)阅羹,該函數(shù)在內(nèi)部以某種形式來(lái)做增強(qiáng)對(duì)象勺疼,最后返回對(duì)象。

function createObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

function enhanceObject(o) {
    var clone = createObject(o)
    clone.talk = function() {}
    return clone
}

function Parent() {}

var parent = new Parent()

var child = enhanceObject(parent)

通過(guò)增強(qiáng)對(duì)象捏鱼,每次創(chuàng)建的新實(shí)例执庐,所擁有的方法不是共享 Parent.prototype 中的,而是各自獨(dú)立創(chuàng)建的导梆。因此轨淌,該方式的特點(diǎn)類似借用構(gòu)造函數(shù)繼承

  • 可添加函數(shù),增強(qiáng)能力
  • 每次創(chuàng)建對(duì)象都會(huì)創(chuàng)建一遍方法

寄生組合式繼承

我們?cè)?組合繼承 中發(fā)現(xiàn)看尼,這種方式最大的缺點(diǎn)是會(huì)調(diào)用兩次父構(gòu)造函數(shù)猿诸,
一次是設(shè)置子類型實(shí)例的原型的時(shí)候:

var parent = new Parent('parent')
Child.prototype = parent

一次在創(chuàng)建子類型實(shí)例的時(shí)候:

var child = new Child('child')

回想下 new 的模擬實(shí)現(xiàn),其實(shí)在這句中狡忙,我們會(huì)執(zhí)行:

Parent.call(this, name)

所以我們?cè)诶龍D中可以發(fā)現(xiàn)梳虽,parentchild 中都有一份 name 屬性。

因此灾茁,通過(guò) 在 寄生組合式繼承 中的 createObject 方法窜觉,間接的讓 Child.prototype 訪問(wèn)到 Parent.prototype,從而減少調(diào)用父構(gòu)造函數(shù)的次數(shù)北专。

function createObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

function Parent(name) {
    this.name = name
}

function Child(name) {
    Parent.call(this, name)
}

Child.prototype = createObject(Parent.prototype)
Child.prototype.constructor = Child

var child = new Child('child')

例圖如下:

QQ20191116-220051@2x.png

這種方式的高效率體現(xiàn)它只調(diào)用了一次 Parent 構(gòu)造函數(shù)禀挫,并且因此避免了在 Parent.prototype 上面創(chuàng)建不必要的、多余的屬性拓颓。與此同時(shí)语婴,原型鏈還能保持不變;因此驶睦,還能夠正常使用 instanceof 和 isPrototypeOf砰左。開(kāi)發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類型最理想的繼承范式。

后記

從 Prototype 開(kāi)始說(shuō)起 一共分為兩篇场航,從兩個(gè)角度來(lái)講述 JavaScript 原型相關(guān)的內(nèi)容缠导。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市溉痢,隨后出現(xiàn)的幾起案子僻造,更是在濱河造成了極大的恐慌憋他,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件髓削,死亡現(xiàn)場(chǎng)離奇詭異竹挡,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)立膛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門揪罕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人旧巾,你說(shuō)我怎么就攤上這事耸序∪绦” “怎么了鲁猩?”我有些...
    開(kāi)封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)罢坝。 經(jīng)常有香客問(wèn)我廓握,道長(zhǎng),這世上最難降的妖魔是什么嘁酿? 我笑而不...
    開(kāi)封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任隙券,我火速辦了婚禮,結(jié)果婚禮上闹司,老公的妹妹穿的比我還像新娘娱仔。我一直安慰自己,他們只是感情好游桩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布牲迫。 她就那樣靜靜地躺著,像睡著了一般借卧。 火紅的嫁衣襯著肌膚如雪盹憎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天铐刘,我揣著相機(jī)與錄音陪每,去河邊找鬼。 笑死镰吵,一個(gè)胖子當(dāng)著我的面吹牛檩禾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疤祭,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼锌订,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了画株?” 一聲冷哼從身側(cè)響起辆飘,我...
    開(kāi)封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤啦辐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蜈项,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芹关,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年紧卒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侥衬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跑芳,死狀恐怖轴总,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情博个,我是刑警寧澤怀樟,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站盆佣,受9級(jí)特大地震影響往堡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜共耍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一虑灰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痹兜,春花似錦穆咐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至谐算,卻和暖如春熟尉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洲脂。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工斤儿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恐锦。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓往果,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親一铅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陕贮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 什么是原型語(yǔ)言 只有對(duì)象,沒(méi)有類;對(duì)象繼承對(duì)象,而不是類繼承類。 “原型對(duì)象”是核心概念潘飘。原型對(duì)象是新對(duì)象的模板肮之,...
    zhoulujun閱讀 2,316評(píng)論 0 12
  • ??面向?qū)ο螅∣bject-Oriented戈擒,OO)的語(yǔ)言有一個(gè)標(biāo)志眶明,那就是它們都有類的概念,而通過(guò)類可以創(chuàng)建任意...
    霜天曉閱讀 2,096評(píng)論 0 6
  • 繼承 Javascript中繼承都基于兩種方式:1.通過(guò)原型鏈繼承筐高,通過(guò)修改子類原型的指向搜囱,使得子類實(shí)例通過(guò)原型鏈...
    LeoCong閱讀 293評(píng)論 0 0
  • 理解 javascript 的原型鏈及繼承 以上所有的運(yùn)行結(jié)果都是 true; 三種構(gòu)造對(duì)象的方法: 通過(guò)對(duì)象字面...
    你期待的花開(kāi)閱讀 1,500評(píng)論 0 3
  • 老屋, 承載了多少童年的美好柑土, 哪怕會(huì)泥土飛揚(yáng)蜀肘, 外面大雨,屋里小雨滴答稽屏, 淋濕了我們的夢(mèng)鄉(xiāng)扮宠。 從前, 木窗里的昏...
    想飛的樹不如草閱讀 112評(píng)論 0 0