JS 对象(5)—原型模式

接上篇 JS对象(4)—构造函数

 

原型模式

 

原型模式

每个函数都有一个prototype(原型)属性,这个属性是一个指针指向一个(原型对象,这个对象包含一些属性和方法,可以由特定类型的所有实例共享。

使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

不必在构造函数中定义对象实例的信息,而是可以将这些信息直接定义到原型对象中。

 1 function Person() {
 2 };
 3 Person.prototype.name = "CC";
 4 Person.prototype.age = 23;
 5 Person.prototype.sayName = function() {
 6     alert(this.name);
 7 };
 8 var person1 = new Person();
 9 var person2 = new Person();
10 console.log(person1.name);    //"CC"
11 console.log(person2.name);    //"CC"
12 console.log(person1.name == person2.name);    //true

 

理解原型

无论什么时候,只要创建了一个函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象

所有原型对象都会自动获得一个constructor属性,这个属性是一个指针指向prototype所在函数

1 Person.prototype    //指向Person的原型对象
2 Person.prototype.constructor    //指向Person

 

创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性,其它属性都是从Object继承来的。

当调用构造函数创建一个新实例后,该实例内部会包含一个指针(内部属性)——[[Prototype]],指向构造函数的原型对象。

注意:这个链接存在于实例与构造函数的原型对象之间。

 

可以通过isPrototypeOf()方法确定对象实例与原型对象之间的这种关系

1 console.log(Person.prototype.isPrototypeOf(person1));    //true

 

Firefox、Safari、Chrome在每个对象上都支持一个__proto__属性,返回[[Prototype]]的值。

1 console.log(person1.__proto__ == Person.prototype);    //true

注意:输入的时候是 shift 加 - 两次,是 "__",而不是"_"。(宝宝之前一直输的“_”,结果各种false。)

 

ECMAScript5新增了一个Object.getPrototypeOf()方法,这个方法返回[[Prototype]]的值。

1 console.log(Object.getPrototypeOf(person1) == Person.prototype);    //true

 

每当代码读取某个对象的属性时,都会执行一次搜索,目标是具有给定名字的属性。

搜索首先从对象实例本身开始,如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,如果在原型对象中找到了这个属性,则返回该属性的值。

这正是多个对象实例共享原型所保存的属性和方法的基本原理。

注意:原型对象最初只有一个constructor属性,而该属性也是共享的,因此可以通过对象实例访问。

1 console.log(person1.constructor == Person);    //true 

 

可以通过对象实例访问原型对象中保存的值,却不能通过对象实例重写原型对象中的值。

如果我们在实例中添加了一个属性,而该属性与原型中的一个属性同名,那我们就是在实例中创建该属性,该属性将会屏蔽原型中的那个属性。

即使将这个属性设置为null,也只会在实例中设置这个属性,不会恢复实例与原型的链接。

不过,使用delete操作符则可以完全删除实例属性,从而能够让我们重新访问原型中的属性。

1 person2.name = "VV";
2 console.log(person2.name);    //"VV"
3 console.log(person1.name);    //"CC"
4 person2.name = null;
5 console.log(person2.name);    //null
6 delete person2.name;
7 console.log(person2.name);    //"CC"

 

使用hasOwnProperty()方法可以检测一个给定的属性是存在于实例中,还是存在于原型中;这个方法(也是从Object继承来的)只有在给定属性存在于实例中时,才会返回true。

1 alert(person2.hasOwnProperty("name"));    //true
2 alert(person1.hasOwnProperty("name"));    //false

 

原型与in操作符

in操作符会在通过对象能够访问给定属性时返回true,无论属性是在实例中还是在原型中。

in操作符结合hasOwnProperty()方法,就能确定一个给定的属性,究竟是存在于实例中,还是存在于原型中。

console.log(person1.name);    //"CC"
console.log("name" in person1);    //true
console.log(person1.hasOwnProperty("name"));    //false

 

更简单的原型语法

用一个包含所有属性和方法的对象字面量重写原型对象。

1 function Person() {
2 };
3 Person.prototype = {
4     name : "CC",
5     age : 23,
6     sayName : function() {
7         alert(this.name);
8     }
9 }; 

以这种方式重写了原型对象,实际上也重写了这个原型对象默认的constructor属性,因此constructor属性变成了新对象的constructor属性,指向了Object构造函数,不再指向Person构造函数。

重写原型对象会造成:

(1)构造函数的prototype属性指向新的原型;

(2)原型对象的constructor属性指向Object;

(3)相当于新原型取代了旧原型,原型链不变,新原型的__proro__指向Object.prototype;

(4)如果在重写原型对象之前创建了实例,那个实例的__proto__指向的是旧原型,定义在新原型上的属性与它无关;

(5)如果是重写之后创建的实例,则__proto__指向的新实例,能够继承原型的一切。

如果constructor值真的很重要,可以将其设置回适当的值。

1 Person.prototype = {
2     constructor : Person,
3     name : "CC",
4     age : 23,
5     sayName : function() {
6         alert(this.name);
7     }
8 };

 

在默认情况下,原生的constructor属性是不可枚举的,也就是说其[Enumerable]特性的值为false;

但是直接在对象上定义的constructor属性,会导致它的[Enumerable]特性的值被设置为true;

在兼容ECMAScript5的前提下,可以这样:

 1 function Person() {
 2 };
 3 Person.prototype = {
 4     name : "CC",
 5     age : 23,
 6     sayName : function() {
 7         alert(this.name);
 8     };
 9 //重设构造函数,只适用于ECMAScript5兼容的浏览器
10 Object.defineProperty(Person.prototype,"constructor",{
11     enumerable : false,
12     value : Person
13 });

 

原型的动态性

可以随时为原型添加属性和方法,并且修改能够立即在所有实例中反应出来。

当调用构造函数创建一个新实例,该实例有一个[[Prototype]]指针,指向构造函数的原型对象;如果重写了原型对象,这个指针依旧指向重写之前的原型,而不会指向重写之后的原型。

也就是说,重写原型会导致:

(1)重写之后的原型依然是构造函数的原型,Person.prototype指向重写之后的原型,切断了构造函数与重写之前的原型的联系;

(2)重写之后的原型的constructor属性指向Object构造函数,而不再指向它本来的Person构造函数;

(3)重写之后的原型与调用构造函数创建的实例没有联系,因为实例的[[Prototype]]指针指向的是重写之前的原型。

 

原生对象的原型

所有的原生引用类型(Object、Array、String,等等)都在其构造函数的原型上定义了方法。

通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法。

不建议在产品化的程序中修改原生对象的原型。

 

原型模式的问题

省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都会取得相同的属性值。

由于属性是被所有实例共享的,对于包含引用类型的属性来说,单个实例属性的改变会直接影响所有实例的属性。

 

组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性,原型模式用于定义方法以及共享的属性。

 1 function Person(name,age) {
 2     this.name = name;
 3     this.age = age
 4 };
 5 Person.prototype = {
 6     constructor : Person,
 7     sayName : function() {
 8         alert(this.name);        
 9     }
10 };

 

下篇 JS 对象(6)—原型链 链接在此!!!

posted @ 2016-07-28 18:38  Aaron_Xiao  阅读(352)  评论(0)    收藏  举报