引自: http://blog.csdn.net/kittyjie/article/details/4380918 原做者解释的浅显易懂,很是不错的JavaScript prototype总结javascript
JS没有提供所谓的类继承,听说在2.0中要加入这种继承方式,可是要全部浏览器都实现2.0的特性那确定又得N多年。昨天看了crockford的一个视频,里面讲解了一下JS的继承方式,按照PPT里面说的,一共分了三类:Prototypal,pseudoclassical,Parasitic Inheritance。java
下面主要介绍一下原型继承:When a function object is created, it is given a prototype member which is an object containing a constructor member which is a reference to the function object. 编程
这里先区别一下什么是prototype属性,和constructor属性。也就是要区别什么是构造器,函数,对象实例。浏览器
其实在JS中构造器就是函数,函数就是构造器,对象实例就是经过var obj=new 函数();这种形式新建出来的实例。区别这些,在说prototype和constructor。从上面的英文中能够看出,prototype是个对象,里面定义了一个constructor,那么咱们能够推论出,constructor是对象实例的属性!而不是函数(构造器)的属性。反过来,prototype是函数(构造器)的属性,而不是实例的属性!app
//在下面这个代码示例中,MyObj是函数(构造器),obj是实例 function MyObj(id){ this.id=id; } var obj=new MyObj(1); alert(MyObj.constructor) //本地代码 alert(obj.constructor) //MyObj.toString() alert(MyObj.prototype) //[object Object] alert(obj.prototype) //undefined
咱们能够看出MyObj是不具备JS意义下的constructor属性的,为何这么说呢。alert(MyObj.constructor)这行代码仍是有东西的:框架
这是由于MyObj是个函数,因此他的构造器就是本地的Function对象,也就是说MyObj是由Function构造出来的。可是这个对咱们意义不大,由于这已经再也不JS层面上了。因此这里能够认为MyObj不具备JS意义下的constrcutor属性。函数
alert(obj.prototype)经过这行咱们能够看出,obj实例是不具备原型属性的。OK,如今区别清楚了这些,能够看原型继承了。若是不区别清楚这个,恐怕下面会看的很晕。this
function Gizmo(id) { this.id = id; } Gizmo.prototype.toString = function () { return "gizmo " + this.id; }; function Hoozit(id) { this.id = id; } Hoozit.prototype = new Gizmo(); Hoozit.prototype.test = function (id) { return this.id === id; };
注意这行:Hoozit.prototype = new Gizmo();这行就是原型继承的核心代码。spa
还有要注意的是只有在new Gizmo()以后,才能添加test等其它方法,这个顺序不能倒过来!若是你先添加了test等方法,而后在new Gizmo(),那么原来添加的那些方法都将找不到了。具体缘由,下面分析完了,也就清楚了。.net
Hoozit.prototype.test = function (id) { return this.id === id; }; Hoozit.prototype = new Gizmo(2); var h=new Hoozit(); alert(h.test(3)); //这里会报错!!
仔细看一下上面的图,这个就是原型继承的图示。左下角new Hoozit(stirng)表明的是新建的一个对象。为了如下表述方便,咱们管他叫objH1。最右边的灰色的箭头就是原型继承链。
根据文章开头的那段英文,咱们知道每一个函数都有一个原型,这个原型是个对象,而且对象里面包含一个constructor属性。其中Object,Gizmo,Hoozit就是函数,能够看出里面都有一个prototype项,而这个prototype又指向一个对象,这个对象里面又有一个constructor属性,constructor又指回自身。
alert(Gizmo.prototype.constructo===Gizmo) //true
可是这里有一个意外,咱们发现Hoozit原型对象里面没有constructor属性,而这个函数的右边却有一个空的对象,里面包含了一个constructor属性?为何呢?
这个问题会发生在原型继承过程当中。主要就是由于Hoozit.prototype = new Gizmo();这句话引发的。这句话的意思就是新建了一个Gizmo对象而且赋给Hoozit的原型!那么,那么,仔细想一想,是否是Hoozit原有的原型对象就被断开了呢??没错,就是这样。因此那个有constructor属性的空对象再也访问不到了!
那如今又有一个疑问了,经过Hoozit.prototype = new Gizmo();这行代码以后,Hoozit.prototype.constructor指向哪里了呢?很简单,知道(new Gizmo()).constructor指向哪里吗?经过上面的图,能够清晰的看出来指向的是Gizmo函数。因此咱们判定,如今的Hoozit.prototype.constructor也指向了那里!
alert(Hoozit.prototype.constructor===Gizmo); //true
上面这行代码验证了咱们的猜想!OK,上面讲完了函数(构造器一边),而后咱们再来讲实例对象这边:每一个实例对象都有一个constructor属性,而且指向构造器(函数)。并且每一个new出来的实例都是某个原型constructor的实例:
var objG1=new Gizmo() alert(objG1 instanceof Gizmo.prototype.constructor) //true alert(Gizmo.prototype.constructor===objG1.constructor); //true
上面为何不拿objH1举例呢,由于他的constructor已经不是他本身的了,而是Gizmo对象的,那么咱们验证一下:
alert(objH1.constructor===objG1.constructor) //true alert(objH1 instanceof Gizmo.prototype.constructor) //true
看到了吗?其实这个问题也不算什么大问题,若是你要不使用instanceof检查父对象或者使用constructor进行原型回溯的话,这个问题能够不解决了。若是想解决这个问题怎么办呢?在Prototype框架的Class.create方法里面给出了一种方法,具体能够参考:http://blog.csdn.net/kittyjie/archive/2009/07/13/4345568.aspx
下面我简单说一下两种方法:
//方法一 //Prototype框架采用了此种方法 Hoozit.prototype = new Gizmo(2); Hoozit.prototype.constructor = Hoozit; //方法二 function Hoozit(id) { this.id = id; this.constructor=Hoozit; } //具体两种方法有什么区别,请参考《JAVASCRIPT语言精髓与编程实践》158~159页。
有兴趣的能够结合上面的图,想一想这两种方法的Hoozit.prototype.constructor应该放在图的什么位置?想不明白的能够和我在交流。
下面看一下《JAVASCRIPT语言精髓和编程实践》书上的一张图(156~157页):
我的认为这张图下面的那个constructor属性的指向是否是有问题?书上面表达的意思确定没有问题,但这张图画的很困惑。和我上面的解释不太同样?先不说这个了,你们本身研究研究吧。
下面咱们再来讲一下我给出的原型继承图中的右边灰色的箭头部分。从这部分可以看出继承的关系。全部未通过变更的函数(构造器)的原型,他们都会继承自Object对象。
alert(Gizmo.prototype instanceof Object.prototype.constructor); //true
这样原型的继承关系就链接起来了。其实说白了,就是一个函数的prototype链向另外一个函数实例,而后不断的这样进行下去,最上面的函数连接Object至对象实例,OK,全部函数就都链接起来了。
PS:
“还有要注意的是只有在new Gizmo()以后,才能添加test等其它方法,这个顺序不能倒过来!“ 这个问题是否是清楚了呢?
================================================
下面看几个例子,说明几个问题:
function Gizmo(id) { this.id = id; this.ask=function(){ alert("gizmo--ask:"+this.id); } function privateMethod(){ return "gizmo--privateMethod"; } privateMethod2=function(){ return "gizmo--privateMethod2"; } } Gizmo.prototype.toString = function () { return "gizmo--toString:" + this.id; }; Gizmo.prototype.id="gizmo3"; function Hoozit(id) { this.id = id; } Hoozit.prototype = new Gizmo("gizmo1"); var g=new Gizmo("gizmo2"); var h=new Hoozit("hoozit");
问题一:
h.ask=function(){ alert("h.ask"); } h.ask(); delete h.ask; //"h.ask" h.ask(); //"gizmo--ask:hoozit" delete h.id h.ask(); //"gizmo--ask:gizmo1" delete Hoozit.prototype.id h.ask(); //"gizmo--ask:gizmo3" /* 这里要说明的问题是:对象是如何找到属性和方法的? 第一步:先在实例对象上找ask方法,找到了,调用。第一个ask说明了这个问题 第二步:若是实例上没有ask方法,在本身的原型对象里面找ask方法,找到调用(没有给出这样的示例) 第三步:若是本身的原型中没有,回溯原型链,在父原型链中找ask方法,找到调用,第二个ask说明了这个问题,这里会一直递归找到Object对象,若是还没找到,那就会报错了 */ /* 再来看属性查找: 首先找实例对象上的属性,因此第二个ask输出"gizmo--ask:hoozit",即id="hoozit" 而后找本身原型中的属性,删除掉h.id以后,找到原型上的属性,因此id="gizmo1" 接着递归原型链,找父对象原型中的属性,一直找到Object对象,因此删除掉Hoozit.prototype.id以后,id="gizmo3" */
问题二:
Gizmo.prototype.question = function () { alert("gizmo--question:" + this.id); }; h.question(); /* 方法能够随时添加,添加完以后就能够调用 */
问题三:
问题四:
问题五: