原型(prototype)无疑是JavaScript中一个十分重要的概念,围绕着原型所涉及的原型链继承、内建对象扩展,JS表现出独特的面向对象特性。
1.什么是原型
每个JS的函数对象中都有一个默认的prototype属性,它指向的就是这个函数对象的原型对象,其初始值是一个没有自身属性“空对象”。(实际上含有指向构造函数的constructor属性以及指向Object.prototype的__proto__属性),来看:
function Test(){ this.a = 'a'; this.b = function(){ return 'b'; } } Test.prototype;
2.对原型进行扩展
对于这个“空”原型对象我们可以进行扩展,添加属性或方法(方法实质也是一种指向匿名函数的特殊属性)从而加强函数的功能。这与在构造函数中添加属性进行扩展的区别是,后者定义的属性在只存在于原型对象里(原型属性或方法仅此一份,类似Java中的类属性或方法)而实例对象本身并没有这个属性(实例对象可以通过原型链__proto__访问到原型的这个属性),在实际应用中的具体表现是原型对象属性具有“实时性”,一旦改变原型对象的属性所有实例对象的这个属性都会变化(因为原本就是一个东东,常说的对象函数复用就是这里啦)。
3.利用实例属性重写原型属性
如果原型属性和实例属性重名,实例属性具有更高优先级,JS引擎会优先查找实例属性返回。另外可以用hasOwnProperty()方法检测某属性究竟是不是自身属性。在查找某对象属性时会按照原型链进行逐级查找(一直到Object.prototype),其原理如下:
每个对象(实例对象、构造函数、原型对象)其实都有__proto__属性。在这里,实例对象的__proto__指向自己的原型对象,构造函数的指向Function.prototype(这里将构造函数看作Function的一个实例),原型对象的__proto__指向Object.prototype(将原型对象看做Object的一个实例),原型链就是通过__proto__层层追溯,直到Object.__proto__=null,直接上图(版权归图片作者所有):
在用for-in循环查找对象的所有属性时,结果所列出的是原型链中的所有实例属性和原型属性。那么问题来了,如何判断一个方法究竟是实例属性还是原型属性呢,这里可以用到obj.hasOwnProperty(propName).另外还可“迂回”通过:propertyIsEnmuberable(propName)方法来判断。
function Triangle(a,b,c){ this.a = a; this.b = b; this.c = c; this.sides = [a,b,c]; this.Perimeter = function(){ return this.a+this.b+this.c; } } Triangle.prototype.dimension = '2d'; Triangle.prototype.getPoints = function(){ return '3'; }
4.isPropertyOf( )方法和__proto__
prototype.isPropertyOf(obj)可用于判断一个实例对象是否是另一个对象的原型,这个方法也可用于判断继承关系(原型链继承的实现就是将子类构造函数的原型对象指向父类的一个实例对象)。
5.利用原型扩展内建对象
我们可以利用扩展的工作原理,对一些内建的构造函数(如Array,String)进行一些扩展,使之具备一些自定义的属性或方法。
例如,字符串中并没有反转方法,我们可以对其原型进行扩展来添加这种方法,这里借助了数组的反转方法String.reverse()
String.prototype.reverse = function(){ Array.prototype.reverse.apply(this.split('').join('')); }
当然这种修改内置对象的做法有可能会导致第三方库在使用内置对象时产生一些错误,或是导致新出现的内置方法被我们的修改覆盖,这时最好在添加前先做一下判断该属性是否已经存在。
6.重写原型对象时的坑
为毛要重写原型?因为很多情况下要实现另一种继承方式。不是已经有原型链继承了么?没错,但是原型链继承方式的子类实例会共享父类实例的引用值属性,两个新new的子类实例如果一个改变了自己的引用属性另一个实例会同样变化(仅限引用值属性,基本值属性还是独立的),所以这个缺陷导致此种继承能应用受限!!!
重写原型对象时注意重置constructor属性,因为新建的对象一般是作为Object的实例而单独存在其prototype.constructor默认指向Object( )(当然也可能是其他已知实例,但同样需要调整防止重写后原型依然指向原来的构造函数).ES5中可以用Object.getPropertyOf( )来检测其自身原型对象究竟是神马。
好了,关于原型的一点总结暂告段落,文中如有错误和不规范的地方还请各位朋友多多指正,谢过先!