Skip to content

[zz]浅析JavaScript原型继承

2009/07/31
好像不大实用,对于不清晰的语法,我是“疑人不用”的。但还是先尽量去了解,使之不疑。
转载自:http://blog.csdn.net/kittyjie/archive/2009/07/26/4380918.aspx

JS没有提供所谓的类继承,据说在2.0中要加入这种继承方式,但是要所有浏览器都实现2.0的特性那肯定又得N多年。昨天看了crockford
的一个视频,里面讲解了一下JS的继承方式,按照PPT里面说的,一共分了三
类:Prototypal,pseudoclassical,Parasitic Inheritance。

下面主要介绍一下原型继承: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是函数(构造器)的属性,而不是实例的属性!

  1. //在下面这个代码示例中,MyObj是函数(构造器),obj是实例  
  2.   
  3. function MyObj(id){  
  4.     this.id=id;  
  5. }  
  6.   
  7. var obj=new MyObj(1);  
  8.   
  9. alert(MyObj.constructor)    //本地代码  
  10. alert(obj.constructor)      //MyObj.toString()  
  11.   
  12. alert(MyObj.prototype)      //[object Object]  
  13. alert(obj.prototype)        //undefined  

//在下面这个代码示例中,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,现在区别清楚了这些,可以看原型继承了。如果不区别清楚这个,恐怕下面会看的很晕。

  1. function Gizmo(id) {  
  2.     this.id = id;  
  3. }  
  4. Gizmo.prototype.toString = function () {  
  5.     return "gizmo " + this.id;  
  6. };  
  7.   
  8. function Hoozit(id) {  
  9.     this.id = id;  
  10. }  
  11. Hoozit.prototype = new Gizmo();  
  12. Hoozit.prototype.test = function (id) {  
  13.     return this.id === id;  
  14. };  

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();这行就是原型继承的核心代码。

 

还有要注意的是只有在new Gizmo()之后,才能添加test等其它方法,这个顺序不能倒过来!如果你先添加了test等方法,然后在new Gizmo(),那么原来添加的那些方法都将找不到了。具体原因,下面分析完了,也就清楚了。

  1. Hoozit.prototype.test = function (id) {  
  2.     return this.id === id;  
  3. };  
  4.   
  5. Hoozit.prototype = new Gizmo(2);  
  6.   
  7. var h=new Hoozit();  
  8. alert(h.test(3));              //这里会报错!!  

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又指回自身。

  1. alert(Gizmo.prototype.constructo===Gizmo) //true  

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也指向了那里!

  1. alert(Hoozit.prototype.constructor===Gizmo); //true  

alert(Hoozit.prototype.constructor===Gizmo); //true

上面这行代码验证了我们的猜测!OK,上面讲完了函数(构造器一边),然后我们再来说实例对象这边:每个实例对象都有一个constructor属性,并且指向构造器(函数)。而且每个new出来的实例都是某个原型constructor的实例:

  1. var objG1=new Gizmo()  
  2. alert(objG1 instanceof Gizmo.prototype.constructor) //true  
  3. alert(Gizmo.prototype.constructor===objG1.constructor); //true  

var objG1=new Gizmo()
alert(objG1 instanceof Gizmo.prototype.constructor) //true
alert(Gizmo.prototype.constructor===objG1.constructor); //true

上面为什么不拿objH1举例呢,因为他的constructor已经不是他自己的了,而是Gizmo对象的,那么我们验证一下:

  1. alert(objH1.constructor===objG1.constructor) //true  
  2. alert(objH1 instanceof Gizmo.prototype.constructor)            //true  

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

下面我简单说一下两种方法:

  1. //方法一  
  2. //Prototype框架采用了此种方法  
  3. Hoozit.prototype = new Gizmo(2);  
  4. Hoozit.prototype.constructor = Hoozit;  
  5.   
  6. //方法二  
  7. function Hoozit(id) {  
  8.     this.id = id;  
  9.     this.constructor=Hoozit;  
  10. }  
  11.   
  12. //具体两种方法有什么区别,请参考《JAVASCRIPT语言精髓与编程实践》158~159页。  

//方法一
//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对象。

  1. alert(Gizmo.prototype instanceof Object.prototype.constructor);    //true  

alert(Gizmo.prototype instanceof Object.prototype.constructor); //true

这样原型的继承关系就连接起来了。其实说白了,就是一个函数的prototype链向另一个函数实例,然后不断的这样进行下去,最上面的函数链接Object至对象实例,OK,所有函数就都连接起来了。

PS:

还有要注意的是只有在new Gizmo()之后,才能添加test等其它方法,这个顺序不能倒过来!“ 这个问题是不是清楚了呢?

 

================================================

 

下面看几个例子,说明几个问题:

  1. function Gizmo(id) {  
  2.     this.id = id;  
  3.     this.ask=function(){  
  4.         alert("gizmo–ask:"+this.id);  
  5.     }  
  6.       
  7.     function privateMethod(){  
  8.         return "gizmo–privateMethod";  
  9.     }  
  10.       
  11.     privateMethod2=function(){  
  12.         return "gizmo–privateMethod2";  
  13.     }  
  14. }  
  15. Gizmo.prototype.toString = function () {  
  16.     return "gizmo–toString:" + this.id;  
  17. };  
  18. Gizmo.prototype.id="gizmo3";  
  19.   
  20. function Hoozit(id) {  
  21.     this.id = id;  
  22. }  
  23. Hoozit.prototype = new Gizmo("gizmo1");  
  24.   
  25.   
  26.   
  27. var g=new Gizmo("gizmo2");  
  28. var h=new Hoozit("hoozit");  

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");

 

问题一:

  1. h.ask=function(){  
  2.     alert("h.ask");  
  3. }  
  4. h.ask();  
  5. delete h.ask;                       //"h.ask"  
  6. h.ask();                           //"gizmo–ask:hoozit"  
  7. delete h.id  
  8. h.ask();                           //"gizmo–ask:gizmo1"  
  9. delete Hoozit.prototype.id  
  10. h.ask();                          //"gizmo–ask:gizmo3"  
  11.   
  12. /* 
  13. 这里要说明的问题是:对象是如何找到属性和方法的? 
  14. 第一步:先在实例对象上找ask方法,找到了,调用。第一个ask说明了这个问题 
  15. 第二步:如果实例上没有ask方法,在自己的原型对象里面找ask方法,找到调用(没有给出这样的示例) 
  16. 第三步:如果自己的原型中没有,回溯原型链,在父原型链中找ask方法,找到调用,第二个ask说明了这个问题,这里会一直递归找到Object对象,如果还没找到,那就会报错了 
  17. */  
  18.   
  19. /* 
  20. 再来看属性查找: 
  21. 首先找实例对象上的属性,所以第二个ask输出"gizmo–ask:hoozit",即id="hoozit" 
  22. 然后找自己原型中的属性,删除掉h.id之后,找到原型上的属性,所以id="gizmo1" 
  23. 接着递归原型链,找父对象原型中的属性,一直找到Object对象,所以删除掉Hoozit.prototype.id之后,id="gizmo3" 
  24. */  

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"
*/

 

问题二:

  1. Gizmo.prototype.question = function () {  
  2.     alert("gizmo–question:" + this.id);  
  3. };  
  4. h.question();  
  5.   
  6. /* 
  7. 方法可以随时添加,添加完之后就可以调用 
  8. */  

Gizmo.prototype.question = function () {
alert("gizmo–question:" + this.id);
};
h.question();

/*
方法可以随时添加,添加完之后就可以调用
*/

 

问题三:

  1. Hoozit.prototype.toString = function () {  
  2.     return "hoozit–toString:" + this.id;  
  3. };  
  4. alert(h);  
  5. delete Hoozit.prototype.toString;  
  6. alert(h);  
  7.   
  8. /* 
  9. 这个问题和问题一有些重复,这里更清楚的看出,删除掉自己原型上的方法之后,就会找父原型中的方法 
  10. */  

Hoozit.prototype.toString = function () {
return "hoozit–toString:" + this.id;
};
alert(h);
delete Hoozit.prototype.toString;
alert(h);

/*
这个问题和问题一有些重复,这里更清楚的看出,删除掉自己原型上的方法之后,就会找父原型中的方法
*/

 

问题四:

  1. h.question.call(g);  
  2. alert(h.toString.call(g));  
  3.   
  4. h.question.apply(g);  
  5. alert(h.toString.apply(g));  
  6.   
  7. /* 
  8. 可以利用apply和call方法把要调用的方法绑定到其它实例。通过结果可以看出上面那种方法调用输出的id是g对象的,而不是h对象的 
  9. */  

h.question.call(g);
alert(h.toString.call(g));

h.question.apply(g);
alert(h.toString.apply(g));

/*
可以利用apply和call方法把要调用的方法绑定到其它实例。通过结果可以看出上面那种方法调用输出的id是g对象的,而不是h对象的
*/

 

问题五:

  1. alert(h.privateMethod());  
  2. alert(g.privateMethod2());  
  3.   
  4. /* 
  5. 上面的任意一个调用都会报错,也就是说通过显示命名函数或者匿名函数但是不加this的声明方式定义在函数之内的函数,是不能被外界访问的。这里一定注意第二种private方法声明,省略了this外面就访问不到了! 
  6. */  
  7.   
  8. /* 
  9. 命名函数:(函数名字为func1) 
  10. function func1(){} 
  11.  
  12. 匿名函数:(注意这里,func1不是函数的名字,仅仅是个别名而已,可以通过func()来调用这个匿名函数) 
  13. func1=function(){} 
  14. */  

alert(h.privateMethod());
alert(g.privateMethod2());

/*
上面的任意一个调用都会报错,也就是说通过显示命名函数或者匿名函数但是不加this的声明方式定义在函数之内的函数,是不能被外界访问的。这里一定注意第二种private方法声明,省略了this外面就访问不到了!
*/

/*
命名函数:(函数名字为func1)
function func1(){}

匿名函数:(注意这里,func1不是函数的名字,仅仅是个别名而已,可以通过func()来调用这个匿名函数)
func1=function(){}
*/

PDF文件下载:

http://download.csdn.net/source/1520257

Advertisements
No comments yet

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: