原文地址:http://www.w3cmm.com/javascript/prototype-object.html
我們創建的每個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個prototype
原型對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。這麼理解的話,prototype就是通過
調用構造函數而創建的那個對象實例的原型對象。使用prototype對象可以讓所有對象實例共享prototype所
包含的屬性和方法。
如圖,Person是一個構造函數,該函數有一個prototype屬性引用Person的原型對象,而person的原型
對象有一個constructor屬性引用Person對象本身。person1和person2是Person對象的實例,他們都有一個
默認的_proto_屬性引用Person對象的prototype對象。那麼Person的prototype對象中的方法和屬性自然可以
被Person的對象訪問到。
無論什麼時候,只要創建了一個新函數,就會根據一組特定的規則爲該函數創建一個prototype屬性,
這個屬性引用函數的原型對象。在默認情況下,所有原型對象都會獲得一個constructor屬性,這個屬性引用
prototype屬性所在的函數。創建自定義的構造函數之後,其原型對象默認只會取得constructor屬性;而其他
方法是從Object繼承的。當調用構造函數創建一個新實例後,該實例的內部會包含一個指向構造函數的原型
對象的指針。這中連接關係存在於實例和構造函數的原型之間,而不是在實例和構造函數之間。可以通過
isPrototypeOf()方法來確定一個原型是不是一個對象的原型對象。
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具有給定名字的屬性。搜索首先從對
象本身開始。如果實例中有給定名字的屬性,則返回該屬性的值;如果沒有,則搜索指針指向的原型對象,
在原型對象中查找給定名字的屬性。如果在原型對象中找到了,則返回屬性的值
雖然可以通過對象實例訪問保存在原型中的值,但不能通過對象實例重寫原型中的值。如果我們在實例中
添加了一個屬性,而該屬性與實例的原型中的一個屬性同名,那相當於在該實例中創建了該屬性,該屬性將會
屏蔽原型中的那麼屬性。
<script type="text/javascript">
function Person() {}
Person.prototype.name = "koji";
Person.prototype.sayName = function() { alert(this.name); }
var p1 = new Person();
var p2 = new Person();
p1.name = "tamaki";
alert(p1.name); //tamaki
alert(p2.name); //koji
</script>
p1通過添加name屬性屏蔽了原型中的name屬性,所以訪問p1.name時返回tamaki。而在訪問p2中的
name屬性時,法相p2中沒有name屬性,就搜索其原型,所以返回的依舊是koji。
當爲對象實例添加一個屬性時,這個屬性會屏蔽原型對象中保存的同名屬性。也就是說,添加這個屬
性只會阻止我們訪問原型中的那個同名屬性,而不會修改原型的那個屬性。通過delete操作符可以刪除實
例屬性,從而使我們能夠重新訪問原型中的屬性。
<script type="text/javascript">
function Person() {}
Person.prototype.name = "koji";
Person.prototype.sayName = function() { alert(this.name); }
var p1 = new Person();
var p2 = new Person();
p1.name = "tamaki";
alert(p1.name); //tamaki
alert(p2.name); //koji
delete p1.name;
alert(p1.name); //koji
</script>
使用hasWwnProperty()方法可以檢測一個屬性是存在於實例中,還是存在於原型中。這個方法
(從Object繼承來的)只在給定屬性存在於對象實例中時,纔會返回true。
<script type="text/javascript">
function Person() {}
Person.prototype.name = "koji";
Person.prototype.sayName = function() { alert(this.name); }
var p1 = new Person();
alert(p1.hasOwnProperty("name")); //false
p1.name = "luo";
alert(p1.name); //luo
alert(p1.hasOwnProperty("name")); //true
delete p1.name;
alert(p1.hasOwnProperty("name")); //false
</script>
通過使用hasOwnProperty()方法,可以判斷訪問的是實例屬性還是原型屬性。在一個對象上調用hasOwnProperty()方法,只有該對象包含該屬性時才返回true。
原型與in操作符
有兩種方式使用in操作符:單獨使用和在for-in循環中使用。在單獨使用時,in操作符在通過對象能夠
訪問給定屬性時返回true,無論該屬性在實例中還是在原型中。
<script type="text/javascript">
function Person() {}
Person.prototype.name = "koji";
Person.prototype.sayName = function() { alert(this.name); }
var p1 = new Person();
alert("name" in p1); // true
p1.name = "luo";
alert("name" in p1); // true
delete p1.name;
alert("name" in p1); // true
</script>
上面的name屬性要麼在對象上訪問到,要麼通過原型訪問到。在使用for-in循環時,返回的是所有能夠通過對象訪問的、可枚舉的屬性,既包括存在於實例中的屬性,
也包括存在於原型中的屬性。屏蔽了原型中不可枚舉屬性的實例屬性也會在for-in循環中返回。
更簡單的原型語法
可以用一個包含所有屬性和方法的對象字面量來重寫整個原型對象。
<script type="text/javascript">
function Person() {}
Person.prototype = {
name : "koji",
sayName : function() {
alert(this.name);
}
}
var p1 = new Person();
alert(p1.name); //koji
p1.sayName(); //koji
</script>
在上面的代碼中,我們將Person.prototype設置爲一個以對象字面量形式創建的新對象。最終結果相同,但有個例外:constructor屬性不再指向Person了。前面說過,每創建一個函數,就會同時創建它的
prototype對象,這個對象也會自動獲得constructor屬性。而在這裏使用的語法,本質上完全重寫了默認的
prototype對象,因此constructor屬性也就變成了新對象的constructor屬性(指向Object構造函數),不再
指向Person函數。如果constructor的值很重要,可以像下面這樣特意將它設置回適當的值:
<script type="text/javascript">
function Person() {}
Person.prototype = {
constructor : Person,
name : "koji",
sayName : function() {
alert(this.name);
}
}
</script>
以上代碼特意包含了一個constructor屬性,並將它的值設置爲Person,從而確保了通過該屬性能夠訪問到適當的值。
原型的動態性
由於在原型中查找值的過程是一次搜索,因此我們對原型對象所做的任何修改都能夠立即從實例上
反映出來——即使是先創建了實例後修改原型也照樣如此。
<script type="text/javascript">
function Person() {}
var p1 = new Person();
Person.prototype.sayHi = function() { alert("hi"); };
p1.sayHi();
</script>
儘管可以隨時爲原型添加屬性和方法,並且修改能夠立即在所有對象實例中放映出來,但如果是重寫這個原型對象,那麼情況就不一樣了。我們知道,調用構造函數時會爲實例添加一個指向最初原型的
_proto_指針,而把原型修改爲另外一個對象就等於切斷了構造函數與最初原型之間的聯繫。
<script type="text/javascript">
function Person() {}
var p1 = new Person(); //指向Person構造函數最初的原型
Person.prototype = { //爲Person構造函數指定了新的原型,並添加了屬性
constructor : Person,
name : "koji",
sayName : function() {
alert(this.name);
}
}
p1.sayName(); //由於最初的原型中並沒有該方法,因此出錯
</script>