淺談JS原型鏈
原型鏈
ECMAScript中描述了原型鏈的概念。我們知道ECMAScript并不像C++,Java那樣使用類,但是對(duì)象仍然可以通過(guò)多種方式創(chuàng)建,其中就有構(gòu)造函數(shù)方式。每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,同時(shí)都有一個(gè)prototype屬性, prototype屬性指向構(gòu)造函數(shù)的原型對(duì)象,它被用來(lái)實(shí)現(xiàn)基于原型的繼承和共享。而原型對(duì)象又都默認(rèn)會(huì)取得一個(gè)constructor屬性,這個(gè)屬性包含一個(gè)指向構(gòu)造函數(shù)(prototype屬性所在函數(shù))的指針。每個(gè)通過(guò)調(diào)用構(gòu)造函數(shù)創(chuàng)建的實(shí)例對(duì)象都擁有一個(gè)指向原型對(duì)象的指針,ECMA-262第5版中叫這個(gè)指針為[[prototype]],雖然在腳本上沒(méi)有標(biāo)準(zhǔn)的方式訪問(wèn)[[prototype]],但Chrome、Firefox和Safari在每個(gè)對(duì)象上都支持一個(gè)屬性_proto_,而在其他實(shí)現(xiàn)中,這個(gè)屬性對(duì)腳本是完全不可見(jiàn)的。假如原型對(duì)象等于另一個(gè)類型的實(shí)例,那么它就擁有指向創(chuàng)建該實(shí)例的構(gòu)造函數(shù)的原型對(duì)象的指針,依此類推,就形成了一條指針鏈,這就是原型鏈的概念。通過(guò)下面的圖形我們可以更清晰地了解原型鏈的概念。
ECMA5中可以使用Object.getPrototypeOf()來(lái)獲取實(shí)例的構(gòu)造函數(shù)的prototype
事實(shí)上,上圖所展示的原型鏈還少一環(huán)。我們知道,所有引用類型默認(rèn)都繼承了Object,而這個(gè)繼承也是通過(guò)原型鏈實(shí)現(xiàn)的。函數(shù)是可調(diào)用的對(duì)象,所有函數(shù)的默認(rèn)原型對(duì)象都是Object的實(shí)例,所以函數(shù)的原型對(duì)象都會(huì)包含一個(gè)指向Object構(gòu)造函數(shù)的原型對(duì)象的指針,也即指向Object.prototype的指針[[prototype]]。這樣就解釋了為什么所有自定義對(duì)象類型都會(huì)繼承toLocaleString()、toString()等Object原型對(duì)象的默認(rèn)方法了。還是來(lái)上圖吧。
當(dāng)然,還有很重要的一點(diǎn)是我們需要注意的:對(duì)象實(shí)例中的指針[[prototype]]只指向原型對(duì)象,并不指向構(gòu)造函數(shù)。
原型語(yǔ)法
通常,我們可以用一個(gè)包含所有屬性和方法的對(duì)象字面量來(lái)重寫(xiě)整個(gè)原型對(duì)象。例如
1
|
function
Person(){}
|
2
|
Person.prototype = {
|
3
|
??
name:
"bella"
,
|
4
|
??
age: 21,
|
5
|
??
sayHello:
function
(){
|
6
|
????
alert(
this
.name);
|
7
|
??
}
|
8
|
}
|
不過(guò),我們需要注意的是,重寫(xiě)之后,構(gòu)造函數(shù)Person的原型對(duì)象的constructor屬性不再指向Person了,因?yàn)樵撜Z(yǔ)法的本質(zhì)是完全重寫(xiě)了默認(rèn)的原型對(duì)象,所以constructor屬性也就變成了新對(duì)象的constructor屬性,指向Object構(gòu)造函數(shù),我們此時(shí)就不能通過(guò)constuctor來(lái)確定對(duì)象的類型了。
可以通過(guò)Person.prototype.constructor = Person恢復(fù)constructor的指針。
原型的動(dòng)態(tài)性
原型在查找值的過(guò)程中是一次搜索,當(dāng)我們想引用一個(gè)對(duì)象的某個(gè)屬性時(shí),所引用到的是原型鏈中包含該屬性名的第一個(gè)對(duì)象所對(duì)應(yīng)的屬性值。換句話說(shuō),直接引用這個(gè)屬性的對(duì)象會(huì)首先被查詢是否包含該屬性名,如果包含,該屬性值就是我們想獲取的,查詢停止,如果不包含,會(huì)接著查詢?cè)搶?duì)象的原型是否包含該屬性,依此類推。
我們可以隨時(shí)動(dòng)態(tài)地為原型添加屬性和方法,而且,基于這種搜索過(guò)程,我們對(duì)原型對(duì)象所做的任何修改都能立即從對(duì)象實(shí)例上看到,即使該修改是在創(chuàng)建實(shí)例之后。但如果是用上面提到的語(yǔ)法重寫(xiě)整個(gè)原型對(duì)象就另當(dāng)別論了。因?yàn)橹貙?xiě)原型對(duì)象會(huì)切斷現(xiàn)有原型對(duì)象與原來(lái)已經(jīng)存在的任何對(duì)象實(shí)例之間的聯(lián)系,它們包含的指針[[prototype]]仍然指向原來(lái)的原型對(duì)象,我們可以看看下面的小例子。
01
|
function
Person(){}
|
02
|
var
person1 =
new
Person();
|
03
|
Person.prototype = {
|
04
|
??
name:
"bella"
,
|
05
|
??
age: 21,
|
06
|
??
sayHello:
function
(){
|
07
|
????
alert(
this
.name);
|
08
|
??
}
|
09
|
}
|
10
|
person1.sayHello();?
//error
|
上面的例子中,我們先創(chuàng)建了Person的一個(gè)實(shí)例對(duì)象person1,然后重寫(xiě)了Person的原型對(duì)象,之后再調(diào)用person1.sayHello()就會(huì)發(fā)生錯(cuò)誤。因?yàn)閜erson1中包含的指針[[prototype]]仍然指向原來(lái)的原型對(duì)象,并不包含新的原型對(duì)象中定義的sayHello屬性。
原型的問(wèn)題
原型模式使得所有對(duì)象實(shí)例在默認(rèn)情況下取得相同的屬性值,對(duì)于屬性值為函數(shù)的情況,這正是我們希望看到的,所有對(duì)象實(shí)例共享這一函數(shù)而不需要重復(fù)定義,但是對(duì)于屬性值為基本值的情況,我們通常希望不同的對(duì)象實(shí)例擁有不同的基本值,不過(guò),我們可以通過(guò)在對(duì)象實(shí)例上添加同名屬性來(lái)隱藏原型對(duì)象中的屬性。但是,如果包含引用類型值的屬性,問(wèn)題就顯現(xiàn)出來(lái)了。
01
|
function
Person(){}
|
02
|
Person.prototype = {
|
03
|
??
name:
"bella"
,
|
04
|
??
age: 21,
|
05
|
??
classmates: [
"Lucy"
,
"Lily"
],
|
06
|
??
sayHello:
function
(){
|
07
|
????
alert(
this
.name);
|
08
|
??
}
|
09
|
}
|
10
|
var
person1 =
new
Person();
|
11
|
var
person2 =
new
Person();
|
12
|
person1.classmates.push(
"Mark"
);
|
13
|
alert(person1.classmates === person2.classmates);?
//true
|
這里,我們?yōu)镻erson.prototype對(duì)象添加了classmates屬性,值為一個(gè)字符串?dāng)?shù)組,然后創(chuàng)建了兩個(gè)對(duì)象實(shí)例person1, person2。由于person1, person2所擁有的classmates屬性其實(shí)是共享原型對(duì)象Person.prototype的classmates屬性得到的,也就是數(shù)組只存在于Person.prototype對(duì)象中,person1和person2引用的是同一個(gè)數(shù)組,對(duì)person1中classmates的修改也會(huì)從person2.classmates中反映出來(lái),這樣會(huì)導(dǎo)致所有對(duì)象實(shí)例共享一個(gè)數(shù)組,這往往不是我們想要的。
以上,我只是簡(jiǎn)單地分析了原型鏈的概念和原型對(duì)象的基本特性,希望能對(duì)大家有小小的幫助,想要更深刻地認(rèn)識(shí)它,當(dāng)然還是得靠大家在實(shí)際項(xiàng)目中去學(xué)習(xí)和體會(huì)。
?
參考資料:Standard ECMA-262,JavaScript高級(jí)程序設(shè)計(jì)。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
