面向对象编程(高级程序设计版)(二)
作者:zccst
三、原型模式
每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特点类型的所有实例共享的属性和方法。换言之,prototype就是通过调用构造函数而创建的那个对象实例的原型对象。
例如:
虽然某些实现中无法访问到内部的__proto__属性,但在所有的实现中都可以通过isPrototypeof()方法来确定对象之间是否存在这种关系。从本质上讲,如果对象的__proto__指向调用isPrototypeof()方法的对象,那么这个方法就返回true,如下所示:
在以上代码执行过程中,name属性要么是直接在对象上访问到的,要么是通过原型访问到的。因此,调用"name" in p1始终都返回true,无论该属性存在于实例中还是存在于原型中。同时使用hasOwnProperty()和in操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中,如下所示:
从图中可以看出,重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;他们引用的仍然是最初的原型。
5,原生对象的原型
原型对象的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。所有原生引用类型(Object,Array,String等)都在其构造函数的原型上定义了方法。例如,在Array.prototype中可以找到sort方法,而在String.prototype中可以找到substring()方法,如下所示:
alert(typeof Array.prototype.sort); // function
alert(typeof String.prototype.substring); // function
通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法。可以像修改自定义对象的原型一样修改原生对象的原型,因此可以随时添加方法。下面的代码就给基本包装类型String添加了一个名为startsWith()的方法:
String.prototype.startsWith = function(text){
return this.indexOf(text) == 0;
};
var msg = "hello world";
msg.startsWith("hello"); // true
提示:不推荐在产品化的程序中修改原生对象的原型。原因是:在另一个支持该方法的实现中运行代码时,就可能会导致命名冲突。而且这样做有可能会意外地重写原生方法。
6,原型对象的问题
原型模式也不是没有缺点。首先,它省略了为购置还是传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题。原型模式的最大问题是由其共享的本性所导致的。
原型中所有属性是被很多实例共享的,这种共享对于还是非常合适。对于那些包含基本值的属性倒也说得过去,毕竟通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而对于包含引用类型的属性来说,问题就比较突出了,看下面的例子:function Person(){}Person.prototype = { constructor:Person, name:"Nicholas", age:29, job:"Software Engineer", friends:["Shelby","Court"], sayName:function(){alert(this.name); }}var p1 = new Person();var p2 = new Person();p1.friends.push("Van");alert(p1.friends); // "Shelby, Court, Van"alert(p2.friends); // 【重要】"Shelby, Court, Van"alert(p1.friends == p2.friends); // true
假如我们的初衷就像这样在所有实例中共享一个数组,那么对这个结果我没有话可说。可是,实例一般都是要有属于自己的全部属性。而这个问题正是我们很少看到有人单独使用原型模式的原因所在。
//两个方法
//hasOwnProperty()
//hasPrototypeProperty()
//覆盖原则
//方式二:字面量写法
Person.prototype = {
name : "nick",
age : 26,
job : 'design',
sayName : function(){
alert(this.name);
}
};
var p1 = new Person();//后定义对象
var p2 = new Person();
//p1.sayName();
//alert(p1.sayName() == p2.sayName());
//console.log(p1.__proto__);//Object { name="nick", age=26, job="design"}
//console.log(p1);//Object { name="nick", age=26, job="design"}
//alert(Person.prototype.isPrototypeOf(p1));//true
//alert(Person.prototype.isPrototypeOf(p2));//true
//尽管instanceof可以返回正确的结果。
//对比先定义对象与后定义对象的区别
console.log(person);//Person {}
console.log(person.constructor);//Person()
alert(person.constructor == Person); //true
alert(person.constructor == Object); //false
console.log(p1);//Object { name="nick", age=26, job="design"}
console.log(p1.constructor);//Object()
alert(p1.constructor == Person);//false 本质是完全重写了默认的原型对象(prototype)。因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。可以通过constructor : Person设回正确的值。
alert(p1.constructor == Object);//true