標籤:alert image 原型鏈 建構函式 指標 typeof 建立 方法 bubuko
在JavaScript中建立的每個函數都有一個prototype(原型)屬性,這個屬性是一個指標,指向一個對象,而這個對象的用途是包含可以由特定類型的所有的執行個體共用的屬性和方法。如果按照字面意思來理解,那麼prototype就是通過調用建構函式而建立那個執行個體的原型對象。使用原型對象的好處是可以讓所有對象執行個體共用它所包含的屬性和方法。換句話說,不必在建構函式中定義對象執行個體的資訊,而是可以將這些資訊直接添加到原型對象中,如下例所示:
function Person(){}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function() { alert(this,name);};var person1 = new Person();person1.sayName(); // "Nicholas"var person2 = new Person();person2.sayName(); // "Nicholas"alert(person1.sayName == person2.sayName); //true
本例中將sayName()方法和所有屬性直接添加到了Person的prototype屬性中,建構函式變成了空函數。即使如此,也仍然可以通過調用建構函式來建立新對象,而且新對象還會具有相同的屬性和方法,同時這些屬性和方法是由所有執行個體共用的。換句話說,person1和person2訪問的都是同一組屬性和同一個sayName()函數。
原型對象的性質
無論什麼時候,只要建立了一個新函數,就會根據一組特定的規則為該函數建立一個 prototype 屬性,這個屬性指向函數的原型對象。在預設情況下,所有原型對象都會自動獲得一個 constructor(建構函式)屬性,這個屬性是一個指向prototype 屬性所在函數的指標。就拿前面的例子來說,Person.prototype.constructor指向Person。而通過這個建構函式,我們還可繼續為原型對象添加其他屬性和方法。
建立了自訂的建構函式之後,其原型對象預設只會取得constructor屬性;至於其他方法,則都是從Object繼承而來的。當調用建構函式建立一個新執行個體後, 該執行個體的內部將包含一個指標 (內部屬性)指向建構函式的原型對象。ECMA-262第5版中管這個指標叫[[Prototype]]。雖然在指令碼中沒有標準的方式訪問 [[Prototype]] 但Firefox、Safari 和Chome在每個對象上都支援一個屬性 __proto__ ;而在其他實現中,這個屬性對指令碼則是完全不可見的。不過,要明確的真正重要的一點就是,這個串連存在於執行個體與建構函式的原型對象之間,而不是存在於執行個體與建構函式之間。
用上例中使用的 Person 建構函式和 Person.prototype 建立執行個體的代碼為例,展示了各個對象之間的關係。
展示了Person建構函式、Person 的原型屬性以及Person現有的兩個執行個體之間的關係。在此,Person.prototype指向了原型對象,而Person.prototype.constructor又指回了Person。原型對象中除了包含constructor屬性之外,還包括後來添加的其他屬性。 Person的每個執行個體——person1和person2都包含一個內部屬性,該屬性僅僅指向了Person.prototype;換句話說,它們與建構函式沒有直接的關係。此外,要格外注意的是,雖然這兩個執行個體都不包含屬性和方法,但我們卻可以調用person1.sayName()。這是通過尋找對象屬性的過程來實現的。
雖然在所有實現中都無法訪問到[[Prototype]],但可以通過isPrototypeOf()方法來確定對象之間是否存在這種關係。從本質上講,如果[[Prototype]]指向調用isPrototypeOf()方法的對象(Person.prototype),那麼這個方法就返回true,如下所示:
alert(Person.prototype.isPrototypeOf(person1)); / /truealert(Person.prototype.isPrototypeOf(person2)); / /true
這裡,用原型對象的isPrototypeOf()方法測試了person1 和person2。 因為它們內部都有一個指向Person.prototype的指標,因此都返回了true。
每當代碼讀取某個對象的某個屬性時,都會執行一次搜尋,目標是具有給定名字的屬性。搜尋首先從對象執行個體本身開始。如果在執行個體中找到了具有給定名字的屬性,則返回該屬性的值;如果沒有找到,則繼續搜尋指標指向的原型對象,在原型對象中尋找具有給定名字的屬性。如果在原型對象中找到了這個屬性,則返回該屬性的值。也就是說,在我們調用person1.sayName()的時候,會先後執行兩次搜尋。首先,解析器會問:“執行個體person1有sayName屬性嗎?” 答:“沒有。” 然後,它繼續搜尋,再問:“person1 的原型有sayName屬性嗎?” 答:“有。” 於是,它就讀取那個儲存在原型對象中的函數。當我們調用person2.sayName()時,將會重現相同的搜尋過程,得到相同的結果。而這正是多個對象執行個體共用原型所儲存的屬性和方法的基本原理。
雖然可以通過對象執行個體訪問儲存在原型中的值,但卻不能通過對象執行個體重寫原型中的值。如果我們在執行個體中添加了一個屬性,而該屬性與執行個體原型中的一個屬性同名,那我們就在執行個體中建立該屬性,該屬性將會屏蔽原型中的那個屬性。
JavaScript 原型鏈學習(一)原型對象