一、理解原型对象
由特定类型创建的所有实例,可能存在公共的属性,也可能存在实例特有的属性。为了保证公共属性不重复定义,将公共属性保存在原型对象中;为了实例特有的属性不共享独立存在,则将特有的属性保存在构造函数中。
如上原型链的原理图所示,构造函数、原型对象和实例之间有以下联系:
- 构造函数的prototype属性指向原型对象
- 原型对象的constructor属性指向构造函数
- 所有实例的__proto__属性指向原型对象
- 所有实例的constructor属性指向构造函数(实例原来没有constructor属性,是从原型对象继承的)
var Person = function(name,age){
this.name = name;
this.age = age;
};
//为原型对象创建say方法,所有实例共享say方法
//相当于 原型对象=Person.prototype; 原型对象.say=functhon(){}
Person.prototype.say = function(){
alert(this.name);
};
var person1 = new Person('ermiao',18);
person1.say(); //输出'ermiao'
//此时构造函数为Person,原型对象为Person.prototype,实例为person1
//原型对象的constructor为构造函数Person
alert(Person.prototype.constructor); //输出Person
//实例的constructor为构造函数Person
alert(person1.constructor); //输出Person
//实例的__proto__为原型对象Person.prototype
alert(person1.__proto__ === Person.prototype); //输出true
注意:
- 实例的__proto__指针在ECMA-262中定义为 [[prototype]],脚本中没有标准的方式访问 [[prototype]]。但是在Firefox、Safari、Chrome中规定每个对象都支持__proto__属性。
- 创建自定义构造函数后,原型对象默认只有constructor属性,其他都是从Object中继承的。
- 由于实例原来没有constructor属性,是从原型对象继承的,因此实例和构造函数没有直接的关系。
二、判断方法
构造函数、原型对象和实例之间有很多判断方法如下:
1.实例和原型对象:
- 原型对象A.isPrototypeOf (实例A) 判断实例A的__proto__是否为原型对象A(返回True/False)
- Object.getPrototypeOf(实例A) 返回实例A的__proto__(ES5新增方法)
2.实例中的属性来自哪里?
实例能够在构造函数中定义独立不共享的属性和方法,也能继承原型对象中共享的属性和方法,因此需要判断实例中的属性到底来自自己独有的还是继承原型对象的。
当查找实例的某个属性时,首先查找实例自己独有的属性,若没有再查找其原型对象的属性。因此当实例和原型对象有同名属性,会先返回实例的属性值,原型对象中的属性值会被‘遮蔽’
实例能够访问原型对象的属性值,但却不能改变原型对象的属性。
- 检测实例中是否有属性A?
单独使用in操作符可以判断实例中是否有属性A(不管是独有还是继承)
alert('name' in person1); //输出true,name是独有属性
alert('say' in person1); //输出true,say方法是继承原型对象的
- 检测实例中的属性A是否是独有?
实例.hasOwnProperty(“属性名A”) 能够判断属性名A是否是实例独有属性,但未定义和继承属性都返回false
alert(person1.hasOwnProperty("name")); //返回true
alert(person1.hasOwnProperty("say")); //返回false,say方法是继承原型对象的属性
alert(person1.hasOwnProperty("school")); //返回false,school属性未定义
- 检测实例中的属性A是否是继承了原型对象?
通过结合in操作符和hasOwnProperty函数,能够创建一个函数直接判断属性是否是继承的:
function hasPrototypeProperty(object,key){
return !object.hasOwnProperty(key) && (key in object);
}
alert(hasPrototypeProperty(person1,"say")); //输出true,say发法是继承的
alert(hasPrototypeProperty(person1,"name")); //输出false,name属性是独有的
- 返回所有属性
遍历实例所有属性的方法有:for-in循环遍历、Object.keys()、Object.getOwnPropertyNames()
(1)for-in循环
for (var p in 对象) 返回所有能够通过对象访问的可枚举(enumerated)的属性(包含实例独有的和原型对象继承的)
在IE8和更早的版本,屏蔽不可枚举属性([[Enumerable]]=false)的实例属性不会出现在for-in循环中。而其他版本, 会出现,因为规定开发人员定义的属性都是可枚举的。ES5将constructor和prototype属性的[[Enumerable]]设为false。
(2)属性数组 = Object.keys(对象)
该方法同for-in返回可枚举的属性数组。若对象是原型对象,返回所有属性,若是实例返回其定义的属性
(3)属性数组 = Object.getOwnPropertyNames(对象)
该方法返回一个包含所有属性的数组,不论是否可枚举。
后两种方法都可替代for-in
三、原型对象的修改问题
1.原型的动态性
由于实例和原型之间的松散连接(指针),当增减原型的属性或方法时,实例能够实时的改变。因为当搜索实例的某个属性时,首先在实例中找是否定义,若没有则搜索实例的原型对象中是否存在。
2.自定义原型对象的修改
若采用向原型对象单独添加属性的方法,需要多次输入,为了方便可以直接定义如下:
Person.prototype = {
name:'ermiao',
age: 18,
say:function(){
alert(this.name);
}
};
但是这种定义会切断实例和对象的连接,因为实例的__proto__仍指向旧原型对象,构造函数指向新的原型对象。
注意:由于我们向Person的原型对象新定义了一个对象,相当于原型对象变成了由对象字面量创建的对象,此时虽然instanceOf的结果不变,但原型对象的constructor已不指向Person而是指向了object构造函数。
如果需要用的constructor属性,那么需要把constructor手动指向Person。原生constructor的[[enumerable]]属性为false,但这种赋值方式会将constructor的[[enumerable]]属性改为true。
2.原生对象的原型修改
原生对象如Array可以被定义原型对象,但不建议修改原生对象的原型属性。