1. 原型模式
??? JavaScript中我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。
??? 如果按照字面意思来理解,那么prototye就是通过调用构造函数而创建的那个对象的原型对象。
??? 使用原型对象的好处就是可以让所有对象实例共享它所包含的属性和方法。
??? 或句话说,就是不必在构造函数中定义对象信息,而是可以将这些信息直接添加到原型对象中。
function Person() {} //将属性和方法添加到Person的prototype属性中,构造函数变成了空函数 Person.prototype.name = "answer"; Person.prototype.age = 22; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function() { alert(this.name); } var person1 = new Person(); person1.sayName(); //answer var person2 = new Person(); person2.sayName(); //answer //不同的实例同享对象原型中的方法 alert(person1. sayName == person2.sayName); //true
?
2. 理解原型
??? 无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数生成一个prototype属性。
??? 在默认情况下,所有prototype属性都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。
??? 如上面Person.prototype.constructor指向Person。而通过这个构造函数,我们还可以继续为原型添加其他属性和方法。
??? 创建了自定义的构造函数后,其原型属性默认只会取得contructor属性;至于其他方法,则都是从Object继承而来。
??? 当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(一般为"_proto_",内部属性),指向构造函数的原型属性。(一些浏览器中,这个属性对脚本可见)
??? 要明确的重要一点就是:就是这个连接存在于实例与构造函数的原型属性之间,而不是存在于实例与构造函数之间。
??? 虽然实例的内部_proto_指针在一些实现中无法访问,但是,所有实现中都可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。
??? 如果对象的_proto_指向调用isPrototypeOf()方法的对象,那么这个方法就返回true,如下所示:
alert(Person.prototype.isProtorypeOf(person1)); //true alert(Person.prototype.isProtorypeOf(person2)); //true
?
3. 查找对象属性的过程
??? 每当代码读取某个对象的某个属性的时候,都会执行一次搜索,目标是具有给定名字的属性。
??? 搜索首先从对象实例本身开始。
??? 如果在实例中找到了具有给定名字的属性,则返回该属性的值;
??? 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找到具有给定名字的属性。
??? 如果在原型对象中查找了这个属性,则返回该属性的值。
??? 比如调用person1.sayName()方法,先在实例person1中查找sayName方法,
??? 如果没有,则继续查找person1的原型有sayName属性吗,如果有,则返回,调用sayName方法。
?
??? 可以通过对象实例访问保存在原型中的值,却不能通过对象实例重写原型中的值。
??? 如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,
??? 那么我们就在实例中创建了该属性,该属性将会屏蔽原型中的那个属性(注意是在访问时屏蔽,而不是重写)。
//以“1”中的Person对象为例 person1.name = "xhc"; //为person1添加name属性 alert(person1.name); //xhc 来自person1实例 alert(person2.name); //answer 来自person2的原型
??? 当为一个对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名的属性;如上所示。
??? 添加的属性会阻止我们访问原型中的值,但不会修改原型中的那个值;如上面所示访问person2.name.
??? 但是可以通过delete操作符完全删除实例属性,从而让我们能够重新访问原型中的属性。
//以”1“中的Person对象为例 person1.name = "xhc"; //为person1添加name属性 alert(person1.name); //xhc 来自person1实例 alert(person2.name); //answer 来自原型 delete person1.name; //删除person1的实例属性 alert(person1.name); //answer 来自原型
?
?4. 实例属性检查
??? 使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中,只有属性存在于对象实例中时,才返回true。(该方法从Object继承而来)
???
//以“1”中"的Person对象为例 alert(person1.hasOwnProperty("name")); //false person1.name = "xhc"; //为实例添加属性 alert(person1.hasOwnProperty("name")); //true delete person1.name; //删除实例属性 alert(person1.hasOwnProperty("name")); //false
?
5. 原型与in操作符
??? 有两种方式使用in操作符:单独使用和在for-in循环中使用。
??? 1) 单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例还是原型中。
????????因此同时使用in操作符和hasOwnProperty()方法,就可以确定一个属性到底是存在于对象实例中,还是对象原型中。
//判断一个属性是存在于原型中 function hasPrototypeProperty(obj, name) { return !(obj.hasOwnProperty(name")) && (name in obj); }
?
?2) 使用for-in循环时,返回的是所有能够通过对象访问的,可枚举的(enumerated)属性,其中即包括存在于实例中的属性,也包括存在于原型中的属性。
?????屏蔽了原型中不可枚举属性(即设置了[[DontEnum]]标记的属性)的实例属性也会在for-in循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的――只有IE除外。
?????IE的JScript实现中存在一个bug,即屏蔽了不可枚举属性的实例属性不会出现在for-in循环中,这导致在IE中,默认不可枚举的属性如:hasOwnProperty(),propertyIsEnumerable(),toLodalString(), toString()和valueOf()。有的浏览器也为constructor和prototype属性打上了[[DonEnum]]标记,但这并不是所有浏览器共同的做法。
var obj = { toString : function() { //屏蔽Object中的不可枚举属性toString方法 return "My Object"; } } for(var prop in obj) { if(prop == "toString") { alert("Found toString"); //在IE中不会显示 } }
?
6. 更简单的原型语法
??? 上面的Person例子中,没添加一个属性和方法都需要敲一遍Person.prototype。为了减少不必要的输入,也为了从视觉上更好地封装原型的功能,
??? 更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。
function Person() {} //将Person的prototype属性设置为一个用对象字面量形式创建的新对象。 Person.prototype = { name : "answer", age : 22 job :"SoftWare Engineer", sayName : function() { alert(this.name); } } var person = new Person(); alert(person instanceof Person); //true alert(person instanceof Objcet); //true alert(person.constructor == Person); //false alert(person.constructor == Object); //true
?
??? 这种方式和”1“中使用Person的最终结果相同,但有一个例外:constructor属性不再指向Person了。
??? 因为,每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性。
??? 而现在,我们本质上完全重写了prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。
??? 因此,尽管instanceof操作符还能返回正确的结果,但通过constructor已经无法确认对象的类型了。
function Person() {} //将Person的prototype属性设置为一个用对象字面量形式创建的新对象。 Person.prototype = { constructor : Person; //将constructor属性设置回为Person构造函数 name : "answer", age : 22 job :"SoftWare Engineer", sayName : function() { alert(this.name); } }
?
7. 原型的动态性
??? 1) 由于在原型中查找值的过程是一次搜索,因此我们对原型对象所作的任何修改都能够立即从实例上反映出来――及时是先创建了实例后修改了原型也照样如此。
??? 因为原型和实例之间是松散链接关系,实例和原型之间的连接不过是一个指针,而不是一个副本。
//以”1“中Person为例 Person person = new Person(); Person.prototype.sayHi() { alert("hi"); } person.sayHi(); //没有问题,即使先创建对象,后为原型添加方法
???? 2) 尽管可以随时为原型添加新的属性和方法,可以在创建的实例中反映出来,可是如果重写整个原型对象,就不一样了。
????????? 因为调用构造函数时会为实例添加一个指向最初原型的_proto_指针,而把原型对象修改为另一个对象就等于切断了构造函数与最初原型之间的联系。
???????? 记住:实例中的指针仅仅指向原型,而不指向构造函数。
?
8. 原生对象的原型??????? ?
??? 原型模式的重要性不仅体现在自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。
??? 所有原生引用类型(Object, Array, String 等)都在其构造函数的原型上定义了方法。
????
alert(typeof Array.prototype.sort); //function
?
??? 通过原生对象的原型,不仅可以取得所有默认方法的引用,而且可以定义新方法。(但不推荐修改原生对象的原型)