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

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