深入理解JavaScript之原型與原型鏈

JavaScript是一門面向?qū)ο蟮恼Z(yǔ)言,繼承是面向?qū)ο蟮囊淮筇匦裕菄?yán)格來(lái)講JavaScript中卻沒(méi)有通常含義上的繼承伤塌,只能模擬繼承,即使ES6之后有了class轧铁,其實(shí)現(xiàn)和其他面向?qū)ο笳Z(yǔ)言依然有本質(zhì)不同每聪。而JavaScript中模擬繼承的方式則是通過(guò)原型鏈

1、什么是原型和原型鏈齿风?

JavaScript中药薯,對(duì)象實(shí)例在創(chuàng)建的時(shí)候會(huì)關(guān)聯(lián)一個(gè)[[Prototype]]屬性,即對(duì)象的原型

ES6之前標(biāo)準(zhǔn)并沒(méi)有定義獲取原型對(duì)象的方法救斑,但是基本所有瀏覽器都支持通過(guò)__proto__來(lái)獲取童本,而ES6之后則提供了Object.getPrototypeOf()方法

當(dāng)我們對(duì)對(duì)象進(jìn)行“對(duì)象.屬性名”操作時(shí),如:

var a = {
    name = 'smartzheng';
}  

console.log(a.name);

這里會(huì)調(diào)用a對(duì)象的[[Get]]屬性脸候,如果a對(duì)象含有name屬性穷娱,那么a.name直接返回這個(gè)屬性,如果a中沒(méi)有該屬性的話就會(huì)使用到原型了:會(huì)在a.__proto__得到的原型對(duì)象中去尋找name屬性运沦,如果還找不到泵额,就會(huì)在a.__proto__.__proto__(如果有的話)中去找...當(dāng)然這也有一個(gè)終點(diǎn),也就是Object.prototype:這就形成了原型鏈

2茶袒、原型鏈和原型繼承

繼續(xù)詳細(xì)分析一下JavaScript中的原型和原型鏈

我們知道梯刚,創(chuàng)建對(duì)象常用的有3種方式:字面量,new和Object.create()薪寓;上面說(shuō)到亡资,對(duì)象在創(chuàng)建的時(shí)候會(huì)有一個(gè)[[Prototype]]屬性,它其實(shí)是對(duì)另一個(gè)對(duì)象的引用向叉,我們稱之為該實(shí)例的原型對(duì)象

分別來(lái)分析一下前面提到的三種方法創(chuàng)建對(duì)象之后默認(rèn)關(guān)聯(lián)的原型對(duì)象:

1)字面量形式

var a = {};  
console.log(a.__proto__ === Object.prototype); //true  
console.log(a.__proto__);

下面是a.__proto__打印出的結(jié)果:

{
    constructor: ?, 
    __defineGetter__: ?,
     __defineSetter__: ?,   
    hasOwnProperty: ?, 
    __lookupGetter__: ?,
    …
}

展開下一級(jí)之后為:

{
    constructor: ? Object()
    hasOwnProperty: ? hasOwnProperty()
    isPrototypeOf: ? isPrototypeOf()
    propertyIsEnumerable: ? propertyIsEnumerable()
    toLocaleString: ? toLocaleString()
    toString: ? toString()
    valueOf: ? valueOf()
    __defineGetter__: ? __defineGetter__()
    __defineSetter__: ? __defineSetter__()
    __lookupGetter__: ? __lookupGetter__()
    __lookupSetter__: ? __lookupSetter__()
    get __proto__: ? __proto__()
    set __proto__: ? __proto__()
}

這里可以發(fā)現(xiàn)幾個(gè)熟悉的方法锥腻,如valueOf、toString母谎、toLocaleString瘦黑;其實(shí)這些都是Object的prototype中的方法,而我們使用字面量方式創(chuàng)建的對(duì)象實(shí)際上在創(chuàng)建時(shí)也默認(rèn)將Object.prototype賦值給了它的__proto__,所以a.__proto__ === Object.prototype為true幸斥,這也是為什么所有對(duì)象都默認(rèn)可以使用前面提到的幾個(gè)方法的原因

2)new

var a = new Object();  
console.log(a.__proto__ === Object.prototype); //true

顯然匹摇,new和字面量創(chuàng)建對(duì)象一樣,都是將Object.prototype賦值給了實(shí)例的原型對(duì)象

3)Object.create()

var a = Object.create(Object.prototype);  
console.log(Object.getPrototypeOf(a) === Object.prototype); //true

可見甲葬,Object.create()是將傳入對(duì)象直接賦值給了新實(shí)例的原型對(duì)象屬性

通過(guò)上面的分析可以看出廊勃,三種方式創(chuàng)建對(duì)象都為實(shí)例默認(rèn)關(guān)聯(lián)了一個(gè)原型對(duì)象,通過(guò)__proto__或者Object.getPrototypeOf()可以獲取经窖,如果是字面量模式坡垫,則新實(shí)例的原型對(duì)象即Object.prototype,但是new和Object.create()的話則可以自定義默認(rèn)關(guān)聯(lián)的原型對(duì)象:

function Foo(){
    //...
}  
var f1 = new Foo();  
var f2 = Object.create(Foo.prototype);

3画侣、“類”冰悠、“構(gòu)造函數(shù)”和“繼承”

最后再看一個(gè)全面的例子:

function Foo(){
    //...
}
var f1 = new Foo();   
var f2 = Object.create(Foo.prototype);  
console.log(Foo.prototype) //{constructor: ? Foo(), __proto__: Object}  
console.log(Foo.prototype.__proto__ === Object.prototype); //true  
console.log(Object.getPrototypeOf(f1) === Foo.prototype); //true  
console.log(Object.getPrototypeOf(f2) === Foo.prototype); //true  

在JavaScript中經(jīng)常把首字母大寫的function稱之為“類”,例如上面的Foo配乱,這里Foo.prototype包含兩個(gè)屬性溉卓,constructor屬性指向Foo()自身,當(dāng)我們使用new調(diào)用Foo()時(shí)搬泥,實(shí)際上執(zhí)行了以下幾步:
1)創(chuàng)建一個(gè)新對(duì)象:

const f1 = {}  

2)設(shè)置新對(duì)象的constructor屬性為構(gòu)造函數(shù)的名稱的诵,設(shè)置新對(duì)象的__proto__屬性指向構(gòu)造函數(shù)的prototype對(duì)象:

f1.constructor = Foo;     
f1.__proto__ = Foo.prototype  

3)使用新對(duì)象調(diào)用函數(shù),函數(shù)中的this被指向新實(shí)例對(duì)象:

Foo.call(f1) 

4)將初始化完畢的新對(duì)象地址佑钾,保存到等號(hào)左邊的變量中

很多人習(xí)慣稱Foo為一個(gè)類,它的實(shí)例f1的原型對(duì)象對(duì)Foo的prototype烦粒,F(xiàn)oo也有一個(gè)__proto__屬性休溶,指向的是Object.prototype,根據(jù)前面的邏輯扰她,在f1若找不到某個(gè)屬性兽掰,則會(huì)在f1.__proto__,也就是Foo.prototype中尋找該屬性徒役,如果從f1.__proto__中取不到對(duì)應(yīng)的屬性孽尽,則會(huì)在f1.__proto__.__proto__即Object.prototype中尋找該屬性,若還找不到忧勿,則返回undefined

這一表現(xiàn)確實(shí)和繼承很像:有一個(gè)頂級(jí)的父類Object杉女,所有對(duì)象都是它的實(shí)例或者它子類的實(shí)例,所有這些實(shí)例都可以使用這個(gè)頂級(jí)父類中定義的方法鸳吸,比如上面的Foo可以理解成繼承自O(shè)bject熏挎,其實(shí)例f1可以使用Foo.prototype中定義的屬性,如果找不到則使用父類Object.prototype中的屬性晌砾;這雖然看起來(lái)很像面向?qū)ο缶幊讨械睦^承坎拐,但是JavaScript中實(shí)際上沒(méi)有繼承(實(shí)際上從new的工作原理看來(lái)JavaScript也算不上有類,并沒(méi)有類實(shí)例的拷貝操作),只是通過(guò)原型鏈的方式實(shí)現(xiàn)了類似繼承的表現(xiàn)

在Java中哼勇,所有類都繼承自O(shè)bject都伪,所有的類的實(shí)例對(duì)象都可以使用Object中的方法,比如hashCode积担,equals等陨晶,但是與JavaScript不同的是,在Java中磅轻,繼承的含義是指每一個(gè)子類中都有父類方法和屬性的一份拷貝珍逸,在父類中定義了一個(gè)方法,子類可以直接使用也可以重寫覆蓋聋溜,但是子類都是調(diào)用的自己的屬性和方法

而在JavaScript中其實(shí)只是建立了對(duì)象之間的關(guān)聯(lián)谆膳,當(dāng)在對(duì)象自己的屬性中找不到想要獲取的屬性時(shí),就會(huì)去關(guān)聯(lián)在它的__proto__屬性上的對(duì)象中去找撮躁,更像是一種委托

4漱病、總結(jié)

下面這張圖很形象的說(shuō)明了文中各個(gè)角色的關(guān)系:

原型和原型鏈.png

1)每一個(gè)函數(shù)通過(guò)new會(huì)創(chuàng)建一個(gè)以該函數(shù)的prototype為原型對(duì)象的實(shí)例,可以通過(guò)對(duì)象的__proto__屬性獲取該原型對(duì)象
2)函數(shù)的prototype中有兩個(gè)屬性把曼,constructor指向自身杨帽,具體調(diào)用時(shí)機(jī)和方式參見上文關(guān)于new的分析,另一個(gè)屬性__proto__指向上一級(jí)關(guān)聯(lián)的原型對(duì)象(最高為Object.prototype)
3)當(dāng)實(shí)例觸發(fā)[[Get]]操作時(shí)首先從自己內(nèi)部查找屬性嗤军,若找不到則依次通過(guò)__proto__往上找注盈,從而形成了原型鏈

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市叙赚,隨后出現(xiàn)的幾起案子老客,更是在濱河造成了極大的恐慌,老刑警劉巖震叮,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胧砰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡苇瓣,警方通過(guò)查閱死者的電腦和手機(jī)尉间,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)击罪,“玉大人哲嘲,你說(shuō)我怎么就攤上這事∠苯” “怎么了撤蚊?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)损话。 經(jīng)常有香客問(wèn)我侦啸,道長(zhǎng)槽唾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任光涂,我火速辦了婚禮庞萍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忘闻。我一直安慰自己钝计,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布齐佳。 她就那樣靜靜地躺著私恬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪炼吴。 梳的紋絲不亂的頭發(fā)上本鸣,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音硅蹦,去河邊找鬼荣德。 笑死,一個(gè)胖子當(dāng)著我的面吹牛童芹,可吹牛的內(nèi)容都是我干的涮瞻。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼假褪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼署咽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起生音,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤艇抠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后久锥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡异剥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瑟由,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冤寿。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡歹苦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出督怜,到底是詐尸還是另有隱情殴瘦,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布号杠,位于F島的核電站蚪腋,受9級(jí)特大地震影響丰歌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屉凯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一立帖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悠砚,春花似錦晓勇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至枢泰,卻和暖如春描融,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宗苍。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工稼稿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讳窟。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓让歼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親丽啡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谋右,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351