第一步:创建一个空的构造函数。function Person(){}。此时构造函数的prototype属性指向原型对象,而原型对象的constructor属性指向Person构造函数。
第二步:利用对象字面量的方法创建一个Person构造函数的新原型对象。
1 2 3 4 5 6 7 8
Person.prototype={ name:"zzw", age:21, school:"xjtu", sayName:function (){ console.log(this.name); } };
此时,由于创建了Person构造函数的一个新原型对象,所以Person构造函数的prototype属性不再指向原来的原型对象,而是指向了Object构造函数创建的原型对象(这是对象字面量方法的本质)。但是原来的原型对象的constructor属性仍指向Person构造函数。
第三步:由Person构造函数创建一个实例对象。
这个对象实例的constructor指针同构造它的构造函数一样指向新的原型对象。
总结:从上面的这个例子可以看出,虽然新创建的实例对象仍可以共享添加在原型对象里面的属性,但是这个新的原型对象却不再指向Person构造函数而指向Object构造函数,如果constructor的值真的非常重要的时候,我们可以像下面的代码这样重新设置会适当的值:
1 2 3 4 5 6 7 8 9 10
function Person(){} Person.prototype={ constructor:Person, name:"zzw", age:21, school:"xjtu", sayName:function (){ console.log(this.name); } };
这样,constructor指针就指回了Person构造函数。即如下图所示:
值得注意的是:这种方式重设constructor属性会导致它的[[Enumerable]]特性设置位true,而默认情况下,原生的constructor属性是不可枚举的。但是我们可以试用Object.defineProperty()将之修改为不可枚举的(这一部分可以参见我的另一篇博文:《深入理解JavaScript中的属性和特性》)。
F.原生对象的原型原型的重要性不仅体现在自定义类型方面,就连所有原生的引用类型,都是使用这种模式创建的。所有原生引用类型(Object、Array、String,等等)都在其构造函数的原型上定义了方法。例如在Array.prototype中可以找到sort()方法,而在String.prototype中就可以找到substring()方法。
1 2
console.log(typeof Array.prototype.sort);//function console.log(typeof String.prototype.substring);//function
于是,实际上我们是可以通过原生对象的原型来修改它。比如:
1 2 3 4 5
String.prototype.output=function (){ alert("This is a string"); } var message="zzw"; message.output();
这是,便在窗口中弹出了“This is a string”。尽管可以这样做,但是我们不推荐在产品化的程序中修改原生对象的原型。这样做有可能导致命名冲突等问题。
G.原型模式存在的问题