【有书共读】JavaScript面向对象编程指南--第五章
  第五章   原型 
   本章着重介绍函数对象中的原型(prototype)属性。本章主要涉及: 
   1、介绍每个函数都拥有的prototype属性,而该属性所存储的就是原型对象; 
   2、如何为原型对象添加属性; 
   3、如何使用原型对象中的新增属性; 
   4、如何区分对象自身属性与原型属性; 
   5、_proto_介绍,该属性用于保存各对象原型的神秘链接; 
   6、原型方法简介,包括isPrototypeof()、hasOwnProperty()、propertyIsEnumerable()等 
   7、介绍如何(利用原型)强化数组或字符串这样的内建对象(并说明这样做的弊端)。 
   5.1、原型属性 prototype 
   在JavaScript中,函数本身也是一个包含了方法和属性的对象。prototype是函数的另一种属性。 
   在函数定义时被创建的属性中就包括有prototype,它的初始值是一个空对象。 
   我们也可以赋予这个空对象一些方法和属性,但并不会对原函数本身造成什么影响;因为只有当元函数作为构造器使用是,这些属性才会起作用。 
   5.1.1、利用原型添加方法与属性 
   1、在构建构造函数时,添加方法与属性: 
   2.添加属性和方法的另一种方式:通过构造器函数的prototype的属性来增加该构造器所能提供的功能 
   举例:为第一点的构造器增加两个属性(price和rating)和一个方法(getInfo()) 
   如果不需要逐条添加,也可以定义一个对象,将其覆盖之前的原型上。 
   Gadget.prototype={ 
   price:100, 
   rating: ... /*and so on...*/ 
   } 
   5.1.2、使用原型的方法与属性 
   在向prototype 属性添加完所有的方法与属性后,可以直接使用构造器创建对象,这样就可以访问之前添加的属性与方法。 
   原型最重要需要理解的是实时(live)性: 
   在javascript中几乎所有对此昂都是通过传引用的方式来传递的,因此所创建的每个新对象试题中并没有一分属于自己的原型副本。意味着我们可以随时修改prototype属性,并且同一构造器创建的所有对象的prototype属性也会同时改变(甚至还会影响在修改之前就已经创建了的那些对象)。 
   如:之前的例子,再向原型中t添加一个新的方法 
   可以看到,在get()方法定义之前已经创建的newtoy对象还是能访问新增的方法: 
   5.1.3、自身属性与原型属性 
   原型的工作原理:当访问对象的某个属性时,JavaScript引擎会遍历该对象的所有属性,并查找该属性,若是找到则则立即使用属性返回其值。如果没找到,则会去创建该对象的构造函数的原型查找相关的属性。 
   5.1.4、利用自身属性重写原型属性 
   当对象的自身属性与原型属性同名时,对象自身属性的优先级高于原型属性。即:同一个属性名同时出现在对象自身的属性与原型时,会优先取对象自身属性。 
   如下例, name属性优先取了自身属性: 
   可以通过hasOwnProperty()方法判断一个属性是自身属性还是原型属性。 
   > toy.hasOwnProperty('name'); 
   true 
   如果删自身属性,则同名的原型属性生效: 
   > delete toy.name; 
   true 
   > toy.name; 
   "mirror" 
   > toy.hasOwnProperty('name'); 
   false 
   随时可以重建自身属性 
   > toy.name = 'camera'; 
   "camera" 
   > toy.name; 
   "camera" 
   如何判断一个对象的某个原型属性到底是原型链中的哪个原型的属性? 
   答案:使用hasOwnProperty()属性来判断。 
   > toy.toString(); 
   "[object Object]" 
   > toy.hasOwnProperty('toString'); 
   false 
   > toy.constructor.hasOwnProperty('toSrting'); 
   false 
   > toy.constructor.prototype.hasOwnProperty('toString'); 
   false 
   > Object.hasOwnProperty('toString'); 
   false 
   > Object.prototype.hasOwnProperty('toString'); 
   true 
   枚举属性 
   for-in:可以使用for-in循环获取某个对象的所有属性列表 
   for更适合数组,而for-in更适合对象。 
   需要留意: 
   1、并不是所有的属性都会在for-in循环中显示。那些会显示的属性被称为可枚举的。通过枚举propertyIsEnumerable()方法判断对象的某个属性是否可枚举,从而获取对象的显示属性。 
   2、若是原型链中的属性可枚举,也可显示出来。可以通过对象的hasOwnProperty()方法来判断一个属性是对象自身属性还是原型属性; 
   3、对于所有原型的属性,propertyIsEnumerable()都返回false,包括for-in中可枚举的属性。 
   function Gadget(name,color){ 
   this.name = name ; 
   this.color = color; 
   this.getName = function(){ 
   return this.name; 
   }; 
   } 
   Gadget.prototype.price = 100; 
   Gadget.prototype.rating = 3; 
   var newtoy = new Gadget('webcam','black'); 
   propertyIsEnumerable: 
   对所有非内建对象返回true; 
   而对于内见对象与方法,它们大部分是不可枚举的,返回false。 
   另外,任何来自原型链的属性都是不可枚举; 
   但是,如果propertyIsEnumerable()的调用来自原型链上的某个对象,那么该对象中的属性是可枚举的。 
   > newtoy.propertyIsEnumerable('name'); 
   true 
   > newtoy.propertyIsEnumerable('constructor'); 
   false 
   > newtoy.propertyIsEnumerable('price'); 
   false 
   > newtoy.constructor.prototype.propertyIsEnumerable('price'); 
   true 
   5.1.5、isPrototypeOf()方法 
   每个对象都有一个isPrototypeOf()方法,用此方法判断当前的对象是否是另外一个对象的原型。 
   是否可以在不知道某个对象原型是什么的情况下,获得对象的原型呢? 
   大多数浏览器可以,因为大多数浏览器都实现了ES5的Object.getPrototypeOf()方法。 
   对于另一部分实现了ES5部分功能,缺没有欧式线getPrototypeOf()方法的浏览器,我们可以使用特殊属性_proto_. 
   5.1.6、__proto__链接 
   在JavaScript环境中,对象中存在一个指向原型的链接,这个链接被叫做__proto__属性。 
   改写monkey对象做原型的Human()对象构造器 
   创建一个developer对象,并赋予它一些属性; 
   >var developer = new Human(); 
   >developer.feeds = 'pizza'; 
   developer.hacks = 'JavaScript'; 
   "JavaScript" 
   >developer.__proto__ === monkey; 
   true 
   注意: 
   1、__proto__ 只能在学习或调试环境下使用。 
   2、_proto_与prototype并不是等价的。_proto_实际上是某个实例对象的属性,而prototype则是属于构造器函数的属性。 
   5.2、扩展内建对象 
   在JavaScript环境中,内建对象的构造器函数都可以通过对其原型进行扩展。如:只要往数组原型中添加新的方法,就可以使其在所有的数组可用。 
   5.2.1、关于扩展内建对象的讨论 
   由于通过扩展内建对象是非常强大,有了它,我们几乎可以随心所欲地重塑JavaScript语言的能力。但是必须谨慎。因为一旦修改了内建对象,它们的行为会发生变化,代码的用户与维护者就会觉得困惑,从而导致无法预期的错误。 
   当使用自定义方法扩展原型时,首先应该检查该方法是否已经存在。当浏览器内存在同名内建方法是,我们可以直接调用原生方法,就避免了方法覆盖。 
   >if (typeof String.prototype.trim !== 'function'){ 
   String.prototype.trim = function(){ 
   return this.queryReplace(/^\s+|\s+&/g,''); 
   } 
   }; 
   > "hello ".trim(); 
   "hello" 
   5.2、原型陷阱 
   处理原型问题时,需要特别注意以下两种行为: 
   1、当我们对原型对象执行完全替换时,可能会触发原型链中某种异常 
   2、prototype.constructor属性是不可靠的。 
   用一个自定义的新对象完全覆盖掉原有的原型对象; 
   这样会导致原有对象不能访问原型的新增属性,只能依靠_proto_链接来与原有的原型对象保持联系 
   之后创建的所有对象使用的都是被更新后的prototype对象; 
   链接_proto_也指向了新的prototype对象 
   但新对象的constructor属性就不能保持正确了,原本应该是Dog()的引用却指向了Object(); 
   可以通过重置constructor属性来解决以上问题。 
   5.3、本章小结: 
 - 在JavaScript中,所有函数都会拥有一个叫做prototype的属性。默认是为‘空’对象。
 - 可在原型对象中添加新的方法属性,也可以自定义对象来完全替换原有的原型对象。
 - 通过构造器新建对象时(new),该对象会自动拥有指向prototype属性的链接。可通过prototype属性访问相关原型对象的属性。
 - 对象自身属性优先级高于原型对象中的同名属性
 - 可通过hasOwnProperty()方法区分对象自身属性与原型属性
 - 当原型链接中,访问某个对象的属性,JavaScript引擎会先搜索该对象,若对象中不存在该属性,则会继续搜索其他原型的原型,直到最高父级原型Object.prototype。
 - 可对内建的构造器进行扩展,以便所有的对象都能引用添加的功能。在添加相关方法和属性之前,应该检查已有方法是否已存在。这样会增加脚本对未来环境的适应能力。
 
#Java#
