《JavaScript 核心》(1):對(duì)象熙卡、原型和繼承

本文譯自:JavaScript. The Core. - Dmitry Soshnikov

對(duì)象

ECMAScript 是一門(mén)高度抽象化的面向?qū)ο笳Z(yǔ)言,主要和對(duì)象打交道掏湾。雖然也有原始值,但是當(dāng)需要的時(shí)候也會(huì)被轉(zhuǎn)換為對(duì)象肿嘲。

對(duì)象是一個(gè)由屬性組成的集合融击,且有單一的原型。它的原型要么是一個(gè)對(duì)象,要么是 null墓卦。

我們來(lái)看一個(gè)簡(jiǎn)單的對(duì)象示例枚粘。一個(gè)對(duì)象的原型由內(nèi)部的 [[Prototype]] 屬性引用。但是在用戶(hù)級(jí)的代碼中际长,我們用 __proto__ 來(lái)實(shí)現(xiàn)該引用,可以讀作 'dunder proto' 兴泥。

var foo = {
    x: 10,
    y: 20
}

我們會(huì)得到這樣一種結(jié)構(gòu)工育,它有兩個(gè)顯式的自有屬性 xy搓彻。還有一個(gè)隱式的 __proto__ 屬性如绸,指向 foo 的原型。

圖1. 一個(gè)指向原型的對(duì)象

原型有什么用旭贬?我們用原型鏈的概念來(lái)回答這個(gè)問(wèn)題怔接。

原型鏈

原型其實(shí)就是帶有自有屬性的對(duì)象。原型A指向它自身的原型——原型B稀轨,原型B再指向自身的原型——原型C扼脐,直到最終指向的原型為 null。這就稱(chēng)為原型鏈奋刽。

原型鏈?zhǔn)且粋€(gè)由對(duì)象組成的有限鏈瓦侮,用來(lái)實(shí)現(xiàn)繼承共享屬性

假設(shè)我們有兩個(gè)對(duì)象佣谐,它們只有很小一部分有區(qū)別肚吏,其余的部分都一樣。顯然狭魂,一個(gè)設(shè)計(jì)良好的系統(tǒng)會(huì)重用相似的功能/代碼罚攀,而不是在每一個(gè)對(duì)象中重復(fù)一遍。在基于類(lèi)的語(yǔ)言中雌澄,這種代碼重用的語(yǔ)式稱(chēng)為基于類(lèi)的繼承——把相似的功能放入類(lèi) A 斋泄,再創(chuàng)建出繼承自 A 且擁有自身額外小改動(dòng)的 BC

ECMAScript 沒(méi)有類(lèi)的概念掷伙。不過(guò)代碼重用的語(yǔ)式?jīng)]有太大區(qū)別(在有些方面甚至比類(lèi)更加靈活)是己,通過(guò)原型鏈就可以實(shí)現(xiàn)。這種繼承方式稱(chēng)作委托繼承(或者更接近 ECMAScript 的說(shuō)法是任柜,原型繼承)卒废。

和類(lèi) A 沛厨,BC 的例子相似摔认,在 ECMAScript 中逆皮,你會(huì)創(chuàng)建對(duì)象 a, b, c。對(duì)象 a 中存放對(duì)象 bc 的相同部分参袱,bc 中只存放它們自身的額外屬性或方法电谣。

var a = {
    x: 10,
    calculate: function (z) {
        return this.x + this.y + z
    }
}

var b = {
    y: 20,
    __proto__: a
}

var c = {
    y: 30,
    __proto__: a
}

// 調(diào)用繼承方法
b.calculate(30) // 60
c.calculate(40) // 80

很簡(jiǎn)單對(duì)吧?我們可以看到 bc 都能訪(fǎng)問(wèn)對(duì)象 a 中定義的 calculate 方法抹蚀。這正是通過(guò)原型鏈來(lái)實(shí)現(xiàn)的剿牺。

原理很簡(jiǎn)單:如果一個(gè)屬性或方法在對(duì)象自身中無(wú)法找到(比如對(duì)象沒(méi)有自有屬性),那么就嘗試在原型鏈中尋找該屬性/方法环壤。如果在對(duì)象的原型中也找不到該屬性晒来,那么就在原型的原型中找,如此往復(fù)郑现,直到遍歷整個(gè)原型鏈(與基于類(lèi)的繼承做法完全一樣湃崩,當(dāng)解析一個(gè)繼承方法時(shí)——我們也會(huì)找遍類(lèi)型鏈)。第一個(gè)找到的同名屬性/方法將被引用接箫。找到的這個(gè)屬性稱(chēng)作繼承屬性攒读。如果在整個(gè)原型鏈中都找不到這個(gè)屬性,則返回 undefined 辛友。

注意薄扁,在調(diào)用繼承方法時(shí),其中的 this 綁定的是調(diào)用該方法的原始對(duì)象而不是該方法所在的原型對(duì)象废累。在上面的示例中 this.y 的值取自對(duì)象 bc 泌辫,而不是 a 。不過(guò) this.x 的值取自 a 九默,同樣是通過(guò)原型鏈機(jī)制。

如果一個(gè)對(duì)象沒(méi)有明確的指定其原型宾毒,則其 __proto__ 默認(rèn)指向原型 Object.prototype 驼修。

原型 Object.prototype 自身也有 __proto__ 屬性,它指向原型鏈的最后一環(huán) null诈铛。

下圖展示了對(duì)象 a乙各,bc 的繼承結(jié)構(gòu)。

圖2. 原型鏈

注意:

  • ES5中制定了另外一種原型繼承的方法幢竹,使用 Object.create 函數(shù):

    var b = Object.create(a, {y: {value: 20}})
    var c = Object.create(a, {y: {value: 30}})
    
  • 你可以在這一章中獲取更多關(guān)于 ES5 API 的信息耳峦。

  • ES6 已經(jīng)將 __proto__ 納入標(biāo)準(zhǔn),它可以用于對(duì)象的初始化焕毫。

我們經(jīng)常會(huì)需要用到一些有相同或相似聲明結(jié)構(gòu)(比如相同屬性)但聲明值不同的對(duì)象蹲坷。這種情況我們可以使用構(gòu)造函數(shù)驶乾,它能用特定的格式創(chuàng)建對(duì)象。

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

除了用特定格式創(chuàng)建對(duì)象循签,構(gòu)造函數(shù)還有一個(gè)重要的作用 —— 它會(huì)為新創(chuàng)建的對(duì)象自動(dòng)指定一個(gè)原型级乐。這個(gè)原型就存放在 ConstructorFunction.prototype 屬性里。

我們可以用構(gòu)造函數(shù)重寫(xiě)前面例子中的對(duì)象 bc 县匠。這樣风科,Foo.prototype 就扮演了對(duì)象 a 的角色:

// 一個(gè)構(gòu)造函數(shù)
function Foo(y) {
    // 可以用固定格式創(chuàng)建對(duì)象:
    // 他們有后生成的自有 'y' 屬性
    this.y = y
}
// 同時(shí) "Foo.prototype" 里存放著新創(chuàng)建對(duì)象的原型的引用,
// 所以我們可以用它來(lái)定義共享的/繼承的屬性或方法,于是和前面例子一樣乞旦,我們創(chuàng)建:

// 繼承屬性 "x"
Foo.prototype.x = 10

// 還有繼承方法 "calculate"
Foo.prototype.calculate = function (z) {
    return this.x + this.y + z
}

// 再來(lái)用“模板” Foo 創(chuàng)建對(duì)象 "b" 和 "c"
var b = new Foo(20)
var c = new Foo(30)

// 調(diào)用繼承方法
b.calculate(30) // 60
c.calculate(40) // 80

// 來(lái)看看屬性引用是否和預(yù)期的一樣
console.log(
    b.__proto__ === Foo.prototype, // true
    c.__proto__ === Foo.prototype, // true

    // 同時(shí) "Foo.prototype" 自動(dòng)創(chuàng)建一個(gè)特殊屬性 "constructor" 贼穆,
    // 指向構(gòu)造函數(shù)本身;
    // 實(shí)例對(duì)象 "b" 和 "c" 可以透過(guò)委托找到該屬性并且用它來(lái)查看它們的構(gòu)造器兰粉。

    b.constructor === Foo, // true
    c.constructor === Foo, // true
    Foo.prototype.constructor === Foo, // true

    b.calculate === b.__proto__.calculate, // true
    b.__proto__.calculate === Foo.prototype.calculate // true
)

這段代碼可以用下圖的關(guān)系來(lái)表達(dá):

圖3. 構(gòu)造函數(shù)和對(duì)象間的關(guān)系

這張圖再次展示了每一個(gè)對(duì)象都有原型故痊。構(gòu)造函數(shù) Foo 也有自己的 __proto__ ,它指向 Function.prototype 亲桦,而 Function.prototype 又通過(guò) __proto__ 指向 Object.prototype 崖蜜。`

Foo.prototype 就是 Foo 的一個(gè)顯式屬性。它是對(duì)象 bc 的原型客峭。

嚴(yán)格來(lái)說(shuō)豫领,如果要分類(lèi)的話(huà),構(gòu)造函數(shù)和原型的組合可以稱(chēng)作“類(lèi)”舔琅。實(shí)際上等恐,像 Python 的頭等動(dòng)態(tài)類(lèi)的實(shí)現(xiàn)和屬性/方法這種解決方案完全一樣。由此看來(lái)备蚓,Python 中的類(lèi)其實(shí)是 ECMAScript 委托繼承的一種語(yǔ)法糖课蔬。

注意:

  • 在 ES6 中,類(lèi) “class” 的概念已經(jīng)納入標(biāo)準(zhǔn)郊尝,作為上面所述的構(gòu)造函數(shù)的語(yǔ)法糖二跋。由此看來(lái),原型鏈成為了類(lèi)繼承的一種詳細(xì)實(shí)現(xiàn)流昏。
// ES6
class Foo {
    constructor(name) {
        this._name = name;
    }

    getName() {
        return this._name;
    }
}

class Bar extends Foo {
    getName() {
        return super.getName() + ' Doe';
    }
}

var bar = new Bar('John');
console.log(bar.getName()); // John Doe

在 ES3 系列文章的第7章中可以找到這部分內(nèi)容更完整和詳細(xì)的解析扎即。其中分為兩個(gè)部分:7.1.面向?qū)ο缶幊蹋焊耪?/a>,在這部分中你可以找到各種面向?qū)ο缶幊痰姆独驼Z(yǔ)式况凉,以及它們和 ECMAScript 的對(duì)比谚鄙,還有 7.2.面向?qū)ο缶幊蹋篍CMAScript 實(shí)現(xiàn),完全忠于 ECMAScript 中的面向?qū)ο缶幊虒?shí)現(xiàn)刁绒。

現(xiàn)在我們已經(jīng)了解了對(duì)象的基本面闷营,繼續(xù)來(lái)看運(yùn)行時(shí)程序執(zhí)行在 ECMAScript 中如何實(shí)現(xiàn)。這就是所謂的一個(gè)執(zhí)行上下文棧知市,其中的每一個(gè)元素都可以抽象地用對(duì)象來(lái)代表傻盟。沒(méi)錯(cuò)速蕊,ECMAScript 中幾乎所有地方都用對(duì)象的概念運(yùn)作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莫杈,一起剝皮案震驚了整個(gè)濱河市互例,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌筝闹,老刑警劉巖媳叨,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異关顷,居然都是意外死亡糊秆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)议双,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)痘番,“玉大人,你說(shuō)我怎么就攤上這事平痰」眨” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵宗雇,是天一觀的道長(zhǎng)昂芜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)赔蒲,這世上最難降的妖魔是什么泌神? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮舞虱,結(jié)果婚禮上欢际,老公的妹妹穿的比我還像新娘。我一直安慰自己矾兜,他們只是感情好损趋,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著椅寺,像睡著了一般舶沿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上配并,一...
    開(kāi)封第一講書(shū)人閱讀 52,584評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音高镐,去河邊找鬼溉旋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嫉髓,可吹牛的內(nèi)容都是我干的观腊。 我是一名探鬼主播邑闲,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼梧油!你這毒婦竟也來(lái)了苫耸?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤儡陨,失蹤者是張志新(化名)和其女友劉穎褪子,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體骗村,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫌褪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胚股。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笼痛。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖琅拌,靈堂內(nèi)的尸體忽然破棺而出缨伊,到底是詐尸還是另有隱情,我是刑警寧澤进宝,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布刻坊,位于F島的核電站,受9級(jí)特大地震影響即彪,放射性物質(zhì)發(fā)生泄漏紧唱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一隶校、第九天 我趴在偏房一處隱蔽的房頂上張望漏益。 院中可真熱鬧,春花似錦深胳、人聲如沸绰疤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)轻庆。三九已至,卻和暖如春敛劝,著一層夾襖步出監(jiān)牢的瞬間余爆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工夸盟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛾方,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像桩砰,于是被迫代替她去往敵國(guó)和親拓春。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

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

  • 什么是原型語(yǔ)言 只有對(duì)象,沒(méi)有類(lèi);對(duì)象繼承對(duì)象,而不是類(lèi)繼承類(lèi)亚隅。 “原型對(duì)象”是核心概念硼莽。原型對(duì)象是新對(duì)象的模板,...
    zhoulujun閱讀 2,335評(píng)論 0 12
  • 原文:http://dmitrysoshnikov.com/ecmascript/javascript-the-c...
    jaysoul閱讀 478評(píng)論 0 0
  • 對(duì)象與原型鏈 基于類(lèi)和基于原型 我們都知道 JavaScript 是一個(gè)面向?qū)ο蟮恼Z(yǔ)言煮纵,但是它卻沒(méi)有其他諸如 Ja...
    周二可閱讀 562評(píng)論 0 3
  • 作者:Dmitry A. Soshnikov編譯地址:http://dmitrysoshnikov.com/ecm...
    IT程序獅閱讀 3,345評(píng)論 2 12
  • 這兩天輸入有點(diǎn)多懂鸵,比如接納力、比如猶太媽媽的三重角色醉途、比如情商…孩子教育問(wèn)題我從沒(méi)有娘娘開(kāi)始就認(rèn)為不能勝任矾瑰,有一種...
    滿(mǎn)樹(shù)繁花閱讀 378評(píng)論 0 1