原型对象
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。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();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
上述代码首先定义了一个名为Person的构造函数,这个函数有一个一个prototype属性,即Person.prototype,它指向一个对象(原型对象),这个对象中的属性和方法将被所有实例共享。换句话说,由构造函数Person生成的每个实例(person1,person2),初始化后都拥有相同name、age、sayName等属性和方法。上述代码中构造函数、原型对象、实例之间的关系可用下图表示
每个实例都包含一个[[Prototype]]属性,虽然在所有实现中都无法访问到[[Prototype]],但可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。
alert(Person.prototype.isPrototypeOf(person1)); //true
Object.getPrototypeOf()
ECMAScript 5 增加了一个叫Object.getPrototypeOf()的方法,在所有支持的实现中,这个方法返回[[Prototype]]的值。例如:
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
hasOwnProperty()
hasOwnProperty()用于检测一个属性是存在于实例中,还是存在于原型中。这个方法(继承自Object )只在给定属性存在于对象实例中时,才会返回true。
person1.name="Greg";
person1.hasOwnProperty("name") //true
in 操作符:
1、在单独使用时,in 操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
person1.name="Greg";
alert("name" in person1); //true
alert("age" in person1); //true
2、同时使用in 操作符和hasOwnProperty()方法可以确定该属性到底是存在于对象中,还是存在于原型中。
Object.keys()
要取得对象上所有可枚举的实例属性,可以使用ECMAScript 5 的Object.keys()方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组
更简单的原型语法:
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
在上面的代码中,我们将Person.prototype 设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外:constructor 属性不再指向Person 了。
原因在于:在JavaScript中,每创建一个函数,就会同时创建它的prototype 对象,这个对象也会自动获得constructor 属性。而我们在这里使用的语法,本质上完全重写了默认的prototype 对象,因此constructor 属性也就变成了新对象的constructor 属性(指向Object 构造函数),不再指向Person 函数。此时,尽管instanceof操作符还能返回正确的结果,但通过constructor 已经无法确定对象的类型。
原型的动态性
由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来,即使是先创建了实例后修改原型也照样如此。
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi(); //"hi"(没有问题!)
尽管可以随时为原型添加属性和方法,并且修改能够立即在所有c中反映出来,但如果是重写整个原型对象,那么情况就不一样了
function Person(){
}
var friend = new Person();
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error
从下图可以看出,原因在于对象实例的[[Prototype]]属性的指向依然指向于之前的原型对象
原生对象的原型:
Array.prototype 中可以找到sort()方法,而在String.prototype 中可以找到substring()方法
不推荐在产品化的程序中修改原生对象的原型
原型对象的问题:
首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题。原型模式的最大问题是由其共享的本性所导致的。
原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说得过去,毕竟(如前面的例子所示),通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值的属性来说,问题就比较突出了。
组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
参考:《JavaScript高级程序设计第三版》