JavaScript中创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按字面的意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中,如下面的例子所示。
1 function Person() { 2 } 3 4 Person.prototype.name = "Baka"; 5 Person.prototype.age = 23; 6 Person.prototype.job = "Student"; 7 Person.prototype.sayName = function() { 8 alert(this.name); 9 }; 10 11 var person1 = new Person(); 12 person1.sayName(); // "Baka" 13 14 var person2 = new Person(); 15 person2.sayName(); // "Baka" 16 17 alert(person1.sayName == person2.sayName); // ture
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。也就是说,在我们调用person1.sayName()的时候,会先后执行两次搜索。首先,解析器会问:“实例person1有sayName属性吗?” 答:“没有。” 然后,它继续搜索,再问 “person1 的原型有sayName属性吗?” 答:“有。” 于是,它就读取那个保存在原型对象中的函数。当我们调用person2.sayName()时,将会重现相同的搜索过程,得到相同的结果。这正是多个对象实例共享原型所保存的属性和方法的基本原理。
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性:
function Person() { } Person.prototype.name = "Baka"; Person.prototype.age = 23; Person.prototype.job = "Student"; Person.prototype.sayName = function() { alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Akuma"; alert(person1.name); // "Akuma"——来自实例 alert(person2.name); // "Baka"——来自原型
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。即使将这个属性设置为null,也只会在实例中设置这个属性,而不会回复指向原型的连接。不过,使用delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。
1 /* 2 * Person原型定义 3 */ 4 5 var person1 = new Person(); 6 var person2 = new Person(); 7 8 person1.name = "Akuma"; 9 alert(person1.name); // "Akuma" 10 alert(person2.name); // "Baka" 11 12 delete person1.name; 13 alert(person1.name); // "Baka"
使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法只在给定属性存在于对象实例中时,才会返回true。
使用in操作符可以判断属性是否存在于对象实例或者原型实例中。
更简单的原型语法:
1 function Person() { 2 } 3 4 Person.prototype = { 5 name : "Baka", 6 age : 23, 7 job : "Student", 8 sayName : function() { 9 alert(this.name); 10 } 11 };