原文地址: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>