JAVASCRIPT面向对象的程序设计之原型
原型对象
原型是一个对象!
在Javascript中,只要创建一个函数,就会为改函数创建一个prototype属性,这个属性指向函数的原型对象.
默认情况下, 所有原型对象都会自动获得一个constructor(构造函数)属性, 这个属性包含一个指向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(); var person2 = new Person();
在以上这个例子中, 构造函数Person拥有prototype属性, 该属性实质上是一个原型指针, 指向Person Prototype原型;
上图实际不完全, 因为在Person Prototype中还有一个[[Prototype]]指向 Object Prototype(Object构造函数的原型对象);
当通过Person构造函数创建是实例时, 每个实例也都将拥有一个指向Person prototype的特性(Prototype, 在EMCAScript中不能直接访问的属性,称之为特性,即不能直接访问的属性). 因此, 调用person1 的sayName方法 和 person2 的sayName方法都会得到同一个结果"Nicholas".
person1.sayName(); //Nicholas person2.sayName(); //Nicholas
但是如果做了如下操作:
person1.name = "John"; person1.sayName(); //John person2.sayName(); //Nicholas
此时person1的sayName方法的放回结果是John,而person2的sayName方法放回结果仍然是Nicholas.
原因是, 当执行语句 person1.name = "John" 时, 是在给person1对象创建了一个name属性, 此时, 执行sayName方法的时候,首先找到的是实例中的name属性, 而不是原型中的name属性.
加油站:当程序每次去访问一个实例对象的某个属性的时候,都会执行一次搜索, 目标就是我们指定名字的属性。 首先从实例本身开始, 如果在实例中找到了该属性,则返回该属性的值; 如果没有找到, 则继续搜索指针指向的原型对象,在原型对象中查找给定名字的属性。如果在原型对象中找到了该属性, 则返回该属性的值, 否则返回undefined;
如上面的例子: 当调用person1.name 的时候, 首先是查找 person1实例对象的name属性, 所以放回的是 "John"; 当调用person2的时候, 先查找person2的name属性, 发现没有, 在查找原型对象(Person Prototype)中的name属性, 有,所以返回了 "Nicholas";
在如下:
console.log(person1.teacher); //undefined
此时在person1实例 , 原型对象(Person prototype) 和原型对象(Object Prototype)中都没有找到teacher属性, 所以返回的是undefined.
注意:
有一点需要特别注意的是, 实例对象的[[protopye]] 的连接是存在于实例对象和构造函数的原型对象之间的, 而不是存在于实例对象和构造函数之间。
举例:
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();
Person.prototype = {};
person1.sayName(); //Nicholas
var person2 = new Person();
person2.sayName(); //报错,没有该方法
在上面例子的基础上, 加了一条语句Person.prototype = {};
此时我们会发现在调用sayName方法的时候, person1是可以正常执行的, 但是person2则报错了;
原因是, 在执行我们后加的语句的时候, 我们创建了person1对象,此时person1对象的[[prototype]]特性指向了构造函数Person原来的原型对象,就的原型对象中是有sayName方法的; 而person2对象则是在执行了后加语句之后创建的, 这个时候person2指向了一个构造函数Person现在的原型对象, 而该原型对象中没有sayName方法, 所以调用的时候会报错。
这就证明了"注意"中说的, 因为如果说连接是存在于实例对象和构造函数之间的, 那么构造函数的原型对象改变的时候, person1的指向的原型对象也应该发生改变, 然而实际上并没有发生改变, 还是指向了原来的原型对象。
在上面的例子中:
Person.prototype = {};
该语句实际上是通过字面量定义的方式直接重写了构造函数Person的原型对象, 这个时候如果没有显示的声明原型对象的constructor属性,则该属性指向的是Object对象,[[prototype]] 特性则是指向Object Prototype原型对象。
如果想让字面量方式定义的原型对象的constructor属性指向自定义的构造函数, 可以如下声明:
Person.prototype = { constructor: Person };
需要提醒的是, 因为字面量的方式定义构造函数的原型, 实际上就是重写构造函数; 然而, 有些情况下,并不希望重写,这是想在原来的基础上添加属性, 可以如下声明:
Person.prototype.teacher = "John";
Over!
备注: 例子及截图均来自《JAVASCRIPT高级程序设计:第3版》