1. prototype
函数定义的时候,函数本身会默认为该函数创建一个prototype的属性,而如果用new 运算符来生成一个对象的时候就没有prototype属性。而prototype也是一个对象,默认情况下prototype包含了2个属性,一个是constructor,另外一个是[[prototype]](大多数浏览器下显示为__proto__)。constructor属性是一个指向prototype属性所在函数的指针。例如当我们定义如下函数:
function Person(){
}
根据上图可以看出Person对象会自动获得prototyp属性,而prototype也是一个对象,会自动获得一个constructor属性,该属性正是指向Person对象。当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(很多浏览器这个指针名字为__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。
function Person(name){
this.name=name;
}
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');
Person的实例person1中包含了name属性,同时自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法。如下图:
function Person(name){
this.name=name;
}
Person.prototype.share=[];
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');
person1.share.push(1);
person2.share.push(2);
console.log(person2.share); //[1,2]
搜索首先从对象实例开始,如果在实例中找到该属性则返回,如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object仍然没有则返回错误。同样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数。
2. constructor
constructor始终指向创建当前对象的构造函数。我们知道每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。例如:
function Person(name) {
this.name = name;
};
Person.prototype.getName = function() {
return this.name;
};
var p = new Person("haorooms");
console.log(p.constructor === Person); // true
console.log(Person.prototype.constructor === Person); // true
// 将上两行代码合并就得到如下结果
console.log(p.constructor.prototype.constructor === Person); // true
当我们重新定义函数的prototype时(注意和上例的区别,这里不是修改而是覆盖),
function Person(name) {
this.name = name;
};
Person.prototype = {
getName: function() {
return this.name;
}
};
var p = new Person("haorooms");
console.log(p.constructor === Person); // false
console.log(Person.prototype.constructor === Person); // false
console.log(p.constructor.prototype.constructor === Person); // false
为什么呢? 原来是因为覆盖Person.prototype时,等价于进行如下代码操作:
Person.prototype = new Object({
getName: function() {
return this.name;
}
});
而constructor始终指向创建自身的构造函数,所以此时Person.prototype.constructor === Object,即是:
function Person(name) {
this.name = name;
};
Person.prototype = {
getName: function() {
return this.name;
}
};
var p = new Person("haorooms");
console.log(p.constructor === Object); // true
console.log(Person.prototype.constructor === Object); // true
console.log(p.constructor.prototype.constructor === Object); // true
怎么修正这种问题呢?方法也很简单,重新覆盖Person.prototype.constructor即可:
function Person(name) {
this.name = name;
};
Person.prototype = {
getName: function() {
return this.name;
}
};
Person.prototype.constructor = Person;
var p = new Person("haorooms");
console.log(p.constructor === Person); // true
console.log(Person.prototype.constructor === Person); // true
console.log(p.constructor.prototype.constructor === Person); // true
3. [[prototype]]
几乎任何对象有一个[[prototype]]属性,在标准中,这是一个隐藏属性。该属性指向的是这个对象的原型。那么一个对象的[[prototype]]属性究竟怎么决定呢?这是由构造该对象的方法决定的。一般有三种构造一个对象的方法:
1、对象是通过对象字面量构造出来的。
var person1 = {
name: 'cyl',
sex: 'male'
};
形如这个形式的叫做对象字面量。这样子构造出的对象,其[[prototype]]指向Object.prototype。
2、这个对象是由构造函数构造出来的。
function Person(){}
var person1 = new Person();
通过new操作符调用的函数就是构造函数。由构造函数构造的对象,其[[prototype]]指向其构造函数的prototype属性指向的对象。每个函数都有一个prototype属性,其所指向的对象带有constructor属性,这一属性指向函数自身。在本例中,person1的[[prototype]]指向Person.prototype。
3、对象是由函数Object.create构造的。
var person1 = {
name: 'cyl',
sex: 'male'};
var person2 = Object.create(person1);
本例中,对象person2的[[prototype]]指向对象person1。在没有Object.create函数的日子里,人们是这样做的:
Object.create = function(p) {
function f(){}
f.prototype = p;
return new f(); }
然而虽然说[[prototype]]是一个隐藏属性,但很多浏览器都给每一个对象提供
.__proto__这一属性,这个属性就是上文反复提到的该对象的[[prototype]]。由于这个属性不标准,因此一般不提倡使用。ES5中用Object.getPrototypeOf函数获得一个对象的[[prototype]]。ES6中,使用Object.setPrototypeOf可以直接修改一个对象的[[prototype]]。
4. 确定原型和实例的关系
可以通过两种方式来确定原型和实例之间的关系。第一种方式是使用instanceof 操
作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回
true。以下几行代码就说明了这一点。
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
由于原型链的关系,我们可以说instance 是Object、SuperType 或SubType 中任何一个类型的实例。因此,测试这三个构造函数的结果都返回了true。
第二种方式是使用isPrototypeOf()方法。同样,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此isPrototypeOf()方法也会返回true,如下所示。
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
创建了自定义的构造函数之后,其原型对象默认只会取得constructor 属性;至于其他方法,则都是从Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。
虽然在所有实现中都无法访问到[[Prototype]],但可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。从本质上讲,如果[[Prototype]]指向调isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回true。
ECMAScript 5 增加了一个新方法,叫Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值。例如:
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
使用Object.getPrototypeOf()可以方便地取得一个对象的原型,而这在利用原型实现继承的情况下是非常重要的。
支持这个方法的浏览器有IE9+、Firefox 3.5+、Safari 5+、Opera 12+和Chrome。
5. 获取可枚举属性Object.keys()、获取所有属性Object.getOwnPropertyNames()
要取得对象上所有可枚举的实例属性,可以使用ECMAScript 5 的Object.keys()方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。例如:
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"
这里,变量keys 中将保存一个数组,数组中是字符串"name"、"age"、"job"和"sayName"。这个顺序也是它们在for-in 循环中出现的顺序。如果是通过Person 的实例调用,则Object.keys()返回的数组只包含"name"和"age"这两个实例属性。如果你想要得到所有实例属性,无论它是否可枚举,都可以使用Object.getOwnPropertyNames()方法。
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"
注意:结果中包含了不可枚举的constructor 属性。Object.keys()和Object.getOwnPropertyNames()方法都可以用来替代for-in 循环。支持这两个方法的浏览器有IE9+、Firefox 4+、Safari 5+、Opera12+和Chrome。