我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。即原型对象。
通俗一点讲,就是假如有一个类
function Peson(){};//构造函数
Person.prototype.name = "csy";//通过原型对象添加属性和方法
Person.prototype.age = 18;
var person1 = new Person();
console.log(person1.name);//"csy"
由此可见,通过构造函数创建的对象实例可以共享原型对象的所有属性和方法,而不必在构造函数中定义这些属性和方法。
与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。
如何理解原型对象?
只要创建了一个函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。
在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向propertype属性所在的函数的指针。Person.propertype.constructor指向Person,而Person.propertype里面又有一个指针constructor指向Person
通过这个构造函数,我们还可以继续为该原型对象添加其他的属性和方法。costructor是默认创建的,但是其他的方法属性继承于Object.
当调用构造函数创建一个新的实例对象过后,会自动为该实例对象添加一个[Prototype]内部属性,这个属性不能通过脚本直接访问,同样的这个指针指向原型对象。
如何判断对象之间是否存在原型关系,那么就使用方法isPrototypeOf(),例如:Person.prototype.isPrototypeOf(person1),表示,Person.prototype是perosn1的原型对象嘛?如果是则返回ture.
如何获取一个实例对象的原型对象?使用方法Object.getPrototypeOf(),例如:Object.getPrototypeOf(perosn1);返回结果即为person1的原型对象。
查找对象属性的过程:每当代码读取某个对象的某个属性,首先从这个实例对象开始搜索,如果没有搜索到再向指针指向的原型对象搜索,这正是多个实例对象共享原型对象所保存的属性和方法的原理。
关于原型对象属性的重写:
虽然可以通过实例对象访问保存在原型中的值,但却不能通过对象重写原型中的值。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性,虽然被屏蔽,但是依然可以通过其他没有重写该属性的实例对象访问,因此说明在实例中添加这个属性只会阻止我们访问原型中的那个属性,不会修改那个属性。
使用delelte操作符可以删除添加的属性然后恢复对原型对象该属性的访问
如何检测一个属性是存在于实例中还是存在于原型中,只需要使用hasOwnProperty(),这个方法从Object继承而来,只在给定属性存在于对象实例中时才会返回true
原型与in 操作符
in有两种使用方式:for -in 中使用;单独使用。
单独使用时会在通过对象能够访问给定属性时返回true;
比如:“name”in perosn1;
name属性要么在实例对象上可以访问到,要么通过原型可以找到,因此不管name属性存在于实例中还是原型中使用in均能返回true。但是结合hasOwnProperty()可以确定该属性究竟在原型中还是在实例中。
hasPrototypeProperty():取得原型对象上的属性是否存在,例如:
hasOwnPrototypeProperty(person,"name")当属性仅仅在原型对象中存在时返回true,一旦这个属性被添加到了实例对象中,则返回false
在使用for-in循环使用上述方法时获取的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型属性中不可枚举属性的实例属性也会在for-in循环中返回。(IE8除外)
要取得对象上所有可枚举的实例属性,可使用Object.keys()方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
如果想要取得所有的实例属性,无论它是否可枚举,都可以使用Object.getOwnPropertyNames();
例如:var keys = Object.getOwnPropertyNames(Person.prototype);
结果中包含了不可枚举的constructor属性。
注意:Object.keys()和Obejct.getOwnPropertyNames()均可以用来代替for-in循环
更简单的原型语法
如果不想每添加一个属性就敲一遍Perosn.prototype,那么可以考虑以对象字面量的形式来重写整个原型对象
function Person(){}
Person.prototype = {
name:"csy",
age:20,
sayName(){
alert(“name=”+this.name);
}
}
这样做的结果与之前使用Prson.prototype.name="csy”...... 相同,但是唯一不同的是原型对象的constructor不再指向Perosn
相当于重写了原型对象,这导致我们无法通过constructor判断对象的类型,如有必要,我们可以显式地为constructor这个指针赋值
Person.prototype = {
consturctor:Person,
name:"csy",
age:20,
sayName(){
alert(“name=”+this.name);
}
}
这样做又会将constructor的[Enumerable]更改为true,默认情况下,contructor属性是不可枚举的。
同时我们可以使用Object。defineProperty()来更改属性值
Object.defineProperty(Person.prototype,"constructor",{
enumerable:false,
value:Person
})
原型的动态性
我们对原型对象所做的任何修改都能立即反映在实例对象上,即使是先创建了实例再更改。
其原因是实例与原型之间的关系为松散关系,实例与原型之间的连接只不过是一个指针而非副本,因此就可以在原型中找到新添加的属性或方法并返回调用。
但是,如果重写了原型对象情况就不一样了,因为一旦重写了原型对象,原型对象的constructor指针与最初的原型对象之间的联系就被切断了。记住:实例中的指针指向原型而不是constructor,这也就是为什么即使使用了对象字面量重写的原型对象虽然也可以有constructor属性,但是已经不是指向原来的原型对象。
这时假如我们还想从前那样在创建了实例然后添加属性或方法,然后通过添加之前创建的实例对象来调用这个属性或方法,会发生错误。
总结:重写之前的实例对象prototype只能指向最初的原型对象,它们引用的仍然是最初的原型,而在重写之后添加的那些属性和方法,值钱的实例对象肯定也不能访问。
原生对象的原型
原型对象的重要性不仅体现在创建自定义类型方面,就连原生的引用类型,都是采用这种模式创建的
所有原生引用类型(Object Array String等),都在其构造函数的原型上定义了方法
通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法。可以像修改自己定义的对象那样随时添加方法,并且添加的方法在当前环境下都可以调用它。虽然可以这样做,但是并不推荐修改原生对象的原型。既可能会在支持某个方法的实现中产生命名冲突,有可能会意外的重写原生方法
原型对象的缺点
1.忽略了为构造函数传递参数
2.假如原型对象中某个属性包含引用类型,那么某一个实例对象修改这个属性必将导致所有实例对象访问该属性都被修改,这显然不是我们想看到的。我们希望每一个实例都有自己全部的独立的属性,而不会被其他实例所干扰。