無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。在默認(rèn)情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針。就拿前面的例子來(lái)說(shuō),Person.prototype. constructor指向Person。而通過(guò)這個(gè)構(gòu)造函數(shù),我們還可繼續(xù)為原型對(duì)象添加其他屬性和方法。
創(chuàng)建了自定義的構(gòu)造函數(shù)之后,其原型對(duì)象默認(rèn)只會(huì)取得constructor屬性;至于其他方法,則都是從Object繼承而來(lái)的。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)指針(內(nèi)部屬性),指向構(gòu)造函數(shù)的原型對(duì)象。ECMA-262第5版中管這個(gè)指針叫[[Prototype]]。雖然在腳本中沒(méi)有標(biāo)準(zhǔn)的方式訪問(wèn)[[Prototype]],但Firefox、Safari和Chrome在每個(gè)對(duì)象上都支持一個(gè)屬性__proto__;而在其他實(shí)現(xiàn)中,這個(gè)屬性對(duì)腳本則是完全不可見(jiàn)的。不過(guò),要明確的真正重要的一點(diǎn)就是,這個(gè)連接存在于實(shí)例與構(gòu)造函數(shù)的原型對(duì)象之間,而不是存在于實(shí)例與構(gòu)造函數(shù)之間。
以前面使用Person構(gòu)造函數(shù)和Person.prototype創(chuàng)建實(shí)例的代碼為例,圖6-1展示了各個(gè)對(duì)
象之間的關(guān)系。

? ? ? 圖6-1展示了Person構(gòu)造函數(shù)、Person的原型屬性以及Person現(xiàn)有的兩個(gè)實(shí)例之間的關(guān)系。在此,Person.prototype指向了原型對(duì)象,而Person.prototype.constructor又指回了Person。原型對(duì)象中除了包含constructor屬性之外,還包括后來(lái)添加的其他屬性。Person的每個(gè)實(shí)例——person1和person2都包含一個(gè)內(nèi)部屬性,該屬性僅僅指向Person.prototype;換句話說(shuō),它們與構(gòu)造函數(shù)沒(méi)有直接的關(guān)系。此外,要格外注意的是,雖然這兩個(gè)實(shí)例都不包含屬性和方法,但我們卻可以調(diào)用person1.sayName()。這是通過(guò)查找對(duì)象屬性的過(guò)程來(lái)實(shí)現(xiàn)的。
? ? ?雖然在所有實(shí)現(xiàn)中都無(wú)法訪問(wèn)到[[Prototype]],但可以通過(guò)isPrototypeOf()方法來(lái)確定對(duì)象之間是否存在這種關(guān)系。從本質(zhì)上講,如果[[Prototype]]指向調(diào)用isPrototypeOf()方法的對(duì)象(Person.prototype),那么這個(gè)方法就返回true,如下所示:
alert(Person.prototype.isPrototypeOf(person1));? //true
alert(Person.prototype.isPrototypeOf(person2));? //true
Object.getPrototypeOf()
這里,我們用原型對(duì)象的isPrototypeOf()方法測(cè)試了person1和person2。因?yàn)樗鼈儍?nèi)部都有一個(gè)指向Person.prototype的指針,因此都返回了true。
? ? ? ECMAScript 5增加了一個(gè)新方法,叫Object.getPrototypeOf(),在所有支持的實(shí)現(xiàn)中,這個(gè)方法返回[[Prototype]]的值。例如:
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
這里的第一行代碼只是確定Object.getPrototypeOf()返回的對(duì)象實(shí)際就是這個(gè)對(duì)象的原型。第二行代碼取得了原型對(duì)象中name屬性的值,也就是"Nicholas"。使用Object.getPrototypeOf()可以方便地取得一個(gè)對(duì)象的原型,而這在利用原型實(shí)現(xiàn)繼承(本章稍后會(huì)討論)的情況下是非常重要的。支持這個(gè)方法的瀏覽器有IE9+、Firefox 3.5+、Safari 5+、Opera 12+和Chrome。
每當(dāng)代碼讀取某個(gè)對(duì)象的某個(gè)屬性時(shí),都會(huì)執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性。搜索首先從對(duì)象實(shí)例本身開(kāi)始。如果在實(shí)例中找到了具有給定名字的屬性,則返回該屬性的值;如果沒(méi)有找到,則繼續(xù)搜索指針指向的原型對(duì)象,在原型對(duì)象中查找具有給定名字的屬性。如果在原型對(duì)象中找到了這個(gè)屬性,則返回該屬性的值。也就是說(shuō),在我們調(diào)用person1.sayName()的時(shí)候,會(huì)先后執(zhí)行兩次搜索。首先,解析器會(huì)問(wèn):“實(shí)例person1有sayName屬性嗎?”答:“沒(méi)有。”然后,它繼續(xù)搜索,再問(wèn):“person1的原型有sayName屬性嗎?”答:“有。”于是,它就讀取那個(gè)保存在原型對(duì)象中的函數(shù)。當(dāng)我們調(diào)用person2.sayName()時(shí),將會(huì)重現(xiàn)相同的搜索過(guò)程,得到相同的結(jié)果。而這正是多個(gè)8對(duì)象實(shí)例共享原型所保存的屬性和方法的基本原理。
? ? ? 雖然可以通過(guò)對(duì)象實(shí)例訪問(wèn)保存在原型中的值,但卻不能通過(guò)對(duì)象實(shí)例重寫原型中的值。如果我們?cè)趯?shí)例中添加了一個(gè)屬性,而該屬性與實(shí)例原型中的一個(gè)屬性同名,那我們就在實(shí)例中創(chuàng)建該屬性,該屬性將會(huì)屏蔽原型中的那個(gè)屬性。來(lái)看下面的例子。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";12Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg"——來(lái)自實(shí)例alert(person2.name); //"Nicholas"——來(lái)自原型
在這個(gè)例子中,person1的name被一個(gè)新值給屏蔽了。但無(wú)論訪問(wèn)person1.name還是訪問(wèn)person2.name都能夠正常地返回值,即分別是"Greg"(來(lái)自對(duì)象實(shí)例)和"Nicholas"(來(lái)自原型)。當(dāng)在alert()中訪問(wèn)person1.name時(shí),需要讀取它的值,因此就會(huì)在這個(gè)實(shí)例上搜索一個(gè)名為name的屬性。這個(gè)屬性確實(shí)存在,于是就返回它的值而不必再搜索原型了。當(dāng)以同樣的方式訪問(wèn)person2.name時(shí),并沒(méi)有在實(shí)例上發(fā)現(xiàn)該屬性,因此就會(huì)繼續(xù)搜索原型,結(jié)果在那里找到了name屬性。
delete
? ? ?當(dāng)為對(duì)象實(shí)例添加一個(gè)屬性時(shí),這個(gè)屬性就會(huì)屏蔽原型對(duì)象中保存的同名屬性;換句話說(shuō),添加這個(gè)屬性只會(huì)阻止我們?cè)L問(wèn)原型中的那個(gè)屬性,但不會(huì)修改那個(gè)屬性。即使將這個(gè)屬性設(shè)置為null,也只會(huì)在實(shí)例中設(shè)置這個(gè)屬性,而不會(huì)恢復(fù)其指向原型的連接。不過(guò),使用delete操作符則可以完全刪除實(shí)例屬性,從而讓我們能夠重新訪問(wèn)原型中的屬性,如下所示。

在這個(gè)修改后的例子中,我們使用delete操作符刪除了person1.name,之前它保存的"Greg"值屏蔽了同名的原型屬性。把它刪除以后,就恢復(fù)了對(duì)原型中name屬性的連接。因此,接下來(lái)再調(diào)用person1.name時(shí),返回的就是原型中name屬性的值了。
hasOwnProperty()
使用hasOwnProperty()方法可以檢測(cè)一個(gè)屬性是存在于實(shí)例中,還是存在于原型中。這個(gè)方法(不要忘了它是從Object繼承來(lái)的)只在給定屬性存在于對(duì)象實(shí)例中時(shí),才會(huì)返回true。來(lái)看下面這個(gè)例子。

通過(guò)使用hasOwnProperty()方法,什么時(shí)候訪問(wèn)的是實(shí)例屬性,什么時(shí)候訪問(wèn)的是原型屬性就一清二楚了。調(diào)用person1.hasOwnProperty( "name")時(shí),只有當(dāng)person1重寫name屬性后才會(huì)返回true,因?yàn)橹挥羞@時(shí)候name才是一個(gè)實(shí)例屬性,而非原型屬性。圖6-2展示了上面例子在不同情況下的實(shí)現(xiàn)與原型的關(guān)系(為了簡(jiǎn)單起見(jiàn),圖中省略了與Person構(gòu)造函數(shù)的關(guān)系)。

原型與in操作符
有兩種方式使用in操作符:單獨(dú)使用和在for-in循環(huán)中使用。在單獨(dú)使用時(shí),in操作符會(huì)在通過(guò)對(duì)象能夠訪問(wèn)給定屬性時(shí)返回true,無(wú)論該屬性存在于實(shí)例中還是原型中??匆豢聪旅娴睦印?/p>

在以上代碼執(zhí)行的整個(gè)過(guò)程中,name屬性要么是直接在對(duì)象上訪問(wèn)到的,要么是通過(guò)原型訪問(wèn)到的。因此,調(diào)用"name" in person1始終都返回true,無(wú)論該屬性存在于實(shí)例中還是存在于原型中。同時(shí)使用hasOwnProperty()方法和in操作符,就可以確定該屬性到底是存在于對(duì)象中,還是存在于原型中,如下所示。
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
? ? ? 由于in操作符只要通過(guò)對(duì)象能夠訪問(wèn)到屬性就返回true,hasOwnProperty()只在屬性存在于實(shí)例中時(shí)才返回true,因此只要in操作符返回true而hasOwnProperty()返回false,就可以確定屬性是原型中的屬性。下面來(lái)看一看上面定義的函數(shù)hasPrototypeProperty()的用法。