我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象我们叫它原型对象。而这个原型对象的用途是包含可以由特定类型的所有实例共享的属性和方法。它所包含的属性和方法可以被所有对象实例共享。所以说,在定义构造函数时,不必在其中定义属性和方法,而是在原型对象中定义。如下:
function Person{
};
Person.prototype.name = "zhangsan";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
person1.sayName(); //zhangsan
var person2 = new Person();
person2.sayName(); //zhangsan
person1和person2访问的是同一组属性和同一个方法。
1. 原型对象
再说了这么多,接下来了解一下原型对象到底是什么,看图说话:
以上面的例子来说:
创建了Person构造函数。原型对象会默认取得constructor属性。Person.prototype指向Person.prototype.constructor
Person.prototype.constructor指向Person
再看看开头的几句话。结合图再熟悉一下。我们在例子中定义的属性和方法都添加到了原型对象中。实例化的对象Person1和Person2都可以访问,哪怕还有一个Person10,都可以。
我们注意到,实例化的两个对象都不含有属性和方法,却可以调用Person1.sayName().这是通过查找对象属性的过程来实现的。
来看看是怎样进行查找的:
图中我们发现没有什么可以指向Person中的[[Prototype]],但是有isPrototypeOf()方法来确定对象之间是否存在这种关系:
alert(Person.prototype.isPrototypeOf(person1)); //true
所以我们知道实例化的对象内部都有一个指向Person.prototype的指针。
还有一个Object.getPrototypeOf()方法。 [[prototype]]的值
alert(Object.getPrototypeOf(person1) == Person.prototype);
//true
因person1中的[[prototype]]与 Person.prototype指的是同一对象。
那么如果实例对象想定义自己的属性和方法怎么办?会不会担心与原型对象中的命名冲突?
其实在实例中创建与原型对象中相同名字的属性和方法,该属性将会覆盖掉原型对象中的同名属性。
还是刚开始的那段代码,在实例时这样:
var person1 = new Person();
person1.name = "lisi";
person1.name(); //lisi
var person2 = new Person();
person2.sayName(); //zhangsan
在定义了实例对象自己的属性或者方法后,如果不想用了,可以利用delete person1.name删除它。删除之后它和原型对象之间的联系又接上了,相当于什么事都没有发生过。还可以照常访问。
既然现在知道了实例中也可以定义属性和方法,怎样判断属性在实例中还是在原型对象中?hasOwnProperty()方法可以进行判断。
var person1 = new Person();
//person1实例中有名为name的属性吗?false
alert(person1.hasOwnProperty("name")); //false
person1.name = "lisi"; //person1定义一个名为name的属性
//person1实例中有名为name的属性吗?false
alert(person1.hasOwnProperty("name")); //true
var person2 = new Person();
alert(person2.hasOwnProperty("name")); //false
看图说话
2. 原型与in操作符
var person1 = new Person();
//person1实例中有名为name的属性吗?false
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true;
person1.name = "lisi"; //person1定义一个名为name的属性
//person1实例中有名为name的属性吗?false
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true;
由上可以得出结论,无论属性在实例中还是在原型中,不能用in来进行判断。但是同时使用hasOwnProperty()和in就可以准确的进行判断。
for-in循环,返回的是所有可通过对象访问的,可枚举的属性。
要取得对象上所有可枚举的属性,使用Object.keys(对象);
还是以第一个例子为例:
var keys = Object.keys(Person.prototype);
alert(keys) //name, age, job, sayName
var p = new Person();
p.name = "lisi";
p.age = 31;
var keys = Object.keys(p);
alert(keys) //name, age,
如果想要得到所有的实例,无论是否可枚举,使用 Object.getOwnPropertyNames()方法
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys) //constructor, name, age, job, sayName
3. 使用对象字面量来重写原型对象:
function Person{
};
Person.prototype = {
constructor:Person,
name : “zhangsan”,
age : 29,
job : “Software Enginner”,
sayName : function(){
alert(this.name);
}
};
注意黑色加粗部分,因为每创建一个函数,就会同时创建它的prototype对象,因此,这个对象也会自动获得constructor属性,而我们在这里使用的语法完全是重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性。
注意以这种方式设置的constructor属性会导致它的[[Enumerable]]特性是可枚举的,但默认情况下,原生的constructor属性不可枚举。所以使用Object.defineProperty()修改默认属性
//重设构造函数,只适用于ECMAScript5兼容的浏览器
Object.defineProperty(person.prototype, “constructor”, {
enumerable:false;
value:Person
});
4. 原型的动态性
原型的动态性是什么意思。就是你对原型对象所做的修改会立即在实例中反映出来。即使这个对象在修改原型对象属性之前创建。
var person1 = new Person();
Person.prototype.sayHi = function(){
alert('Hi');
}
person1.sayHi(); //Hi
这是因为我们在找sayHi函数时,首先会在实例中寻找,没有的话再向原型对象中寻找。因为原型对象与实例之间的链接只不过是一个指针,在找到之后,会返回最新的sayHi属性,并返回保存在那里的函数。
尽管可以随时为原型添加属性和方法,并且能够立即在对象实例中反映出来,但如果重写整个原型对象,结果就不一样了
function Person{
};
var friend = new Person();
Person.prototype = {
constructor:Person,
name : “zhangsan”,
age : 29,
job : “Software Enginner”,
sayName : function(){
alert(this.name);
}
};
friend.sayName(); //error
这是因为,我们知道每创建一个函数,就会自动生成一个原型对象。
上面当定义了一个Person构造函数时,其实就已经默认的生成了一个原型对象。只不过没有属性和方法。后来实例了一个对象,此对象指向的是当前没有属性和方法的只有constructor属性的一个原型对象。
后来又创建了一个Person的包含属性和方法的原型对象。但是friend却不是指向这个。
简言之就是该Person构造函数包含了自身的原型对象和创建的原型对象。
5. 原生对象的原型
原型模式的重要性不仅体现在创建自定义类型的方面,就连所有原生的引用类型,就是采用这种模式创建的。
如Array.sort()可以在Array.prototype.sort()中也可以访问得到。
所以我们还可以为原型对象的属性和方法进行修改。如添加一个自己定义的函数
Array.prototype.aaa = function(){
alert('hello world');
}
var strings = new Array();
strings.aaa(); //hello world
6. 原型对象的问题:
对于包含引用类型值得属性来说,原型对象的问题比较突出
function Person{
};
Person.prototype = {
constructor:Person,
name : "zhangsan",
age : 29,
job : "Software Enginner",
friends:["aa", "bb"],
sayName : function(){
alert(this.name);
}
};
var friend1 = new Person();
var friend 2= new Person();
friend1.friends.push('cc');
alert(friend1.friends); //aa, bb, cc
alert(friend2.friends); //aa, bb, cc
因为friends属性存在于Person.prototype而非friend1中,所以修改原型对象中的属性,也会反映在其他实例中。但是实例一般都要有属于自己的属性和方法,所以说这也是很少单独使用原型模式的原因了。
所以就会出现组合使用构造函数模式和原型模式创建JavaScript对象。