Javascript教程:什么是原型对象
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()函数。要理解原型模式的工作原理,必须理解ECMAScript中原型对象的性质;
如何理解原型对象
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。就拿前面的例子来说,Person.prototype.constructor指向Person。而通过这个构造函数,我们还可以继续为原型对象添加其它属性和方法;
创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性;至于其它方法,则都是从Object继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。在很多实现中,这个内部属性的名字是_proto_,而且通过脚本可以访问到(在firefox、Safari、Chrome和Flash的ActionScript中,都可以通过脚本访问_proto_);而在其它实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点,就是这个连接存在与实例与构造函数的原型对象之间,而不是存在于实例于构造函数之间;
虽然在某些实现中无法访问到内部的_proto_属性,但在所有实现中都可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。从本质上来讲,如果对象的_proto_指向调用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回true,如下所示:
alert(Person.prototype.isPrototypeOf(person1)); //true; alert(Person.prototype.isPrototypeOf(person2)); //true;
这里,我们用原型对象的isPrototypeOf()方法测试了person1和person2。因为它们内部都有一个指向Person.prototype的指针,因此都返回了true;
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。也就是说,在我们调用person1.sayName()的时候,会先后执行两次搜索。首先,解析器会问:“实例person1有sayName属性吗?”答:“没有。”然后,它继续搜索,再问:“person1的原型有sayName属性吗?”答:“有。”于是,它就读取那个保存在原型对象中的函数。当我们调用person2.sayName()时,将会重现相同的的搜索过程,得到相同的结果。而这正是多个对象共享原型所保存的属性和方法的基本原理;
前面提到过,原型最初值包含constructor属性,而该属性也是共享的,因此可以通过对象实例访问;
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。来看下面的例子:
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.ae = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); } var person1 = new Person(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name); //"Greg" ——来自实例 alert(person2.name); //"Nicholas" ——来自原型
在这个例子中,person1的name被一个新值给屏蔽了。但无论访问person1.name还是访问person2.name都能正常地放回值,即分别是“Greg”(来自对象实例)和“Nicholas”(来自原型)。当在alert()中访问person1.name时,需要读取它的值,因此就会在这个实例上搜索一个名为Name的属性。这个属性确实存在,于是就返回它的值而不必再搜索原型了。当以同样的方式访问person2.name时,并没有在实例上发现该属性,因此就会继续搜索原型,结果在那里找到了name属性;
当为对象实例添加了一个属性时,这个属性就会屏蔽原型对象汇总保存的同名属性;换句话说,添加这个属性只会组织我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性值设置为null,也只会在实例中设置这个属性。而不会回复其指向原型的连接。不会,使用delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性,如下所示:
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 20; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); } var person1 = new Person(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name); //“Greg” ——来自实例 alert(person2.name); //"Nicholas" ——来自原型 delete person1.name; alert(person1.name); //"Nicholas" ——来自原型
在这个修改后的例子中,我们使用delete操作符删除了person1.name,之前它保存的“Greg”值屏蔽了同名的原型属性。把它删除以后,就恢复了原型中name属性的连接。因此,接下来再调用person1.name时,返回的就是原型中的name属性的值了;
使用hasOwnProperty()方法可以检测一个属性是存在与实例中,还是存在于原型中。这个方法(不要忘了它是从Object继承来的)只在给定属性存在于对象实例中时,才会返回true。来看下面这个例子:
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.jog = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false person1.name = "Greg"; alert(person1.name); //"Greg" ——来自实例 alert(person1.hasOwnProperty("name")); //true alert(person2.name); //"Nicholas" ——来自原型 alert(person2.hasOwnProperty("name")); //false delete person1.name; alert(person1.name); //"Nicholas" ——来自原型 alert(person1.hasOwnProperty("name")); //flase
通过使用hasOwnProperty()方法,什么时候访问的是实例属性,什么时候访问的是原型属性就一清二楚了。调用person1.hasOwnProperty(“name”)时,只有当person1重写name属性后才会返回true,因为只有这时候name才是一个实例属性,而非原型属性;
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面量的意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将信息直接添加到原型对象中,如下面的例子所示:Word教程网 | Excel教程网 | Dreamweaver教程网 | Fireworks教程网 | PPT教程网 | FLASH教程网 | PS教程网 |
HTML教程网 | DIV CSS教程网 | FLASH AS教程网 | ACCESS教程网 | SQL SERVER教程网 | C语言教程网 | JAVASCRIPT教程网 |
ASP教程网 | ASP.NET教程网 | CorelDraw教程网 |