Extjs的Ext.extend()引发的Javascript继承讨论

Js中的继承实现依靠原型链来实现的。什么是原型链?Javascript中的构造函数有个prototype属性,这个属性返回原型对象的引用,用于提供对象的类的一些基本操作和固有的属性,这里可以参照前一篇文章

举个例子:

1 var Person = function(name){   
2     this.name = name;
3 };
4 Person.prototype = {
5     //可以在这里提供Person的基本功能
6     getName : function(){
7         return this.name;
8     }
9 }


属性继承的本质就是一个对象可以访问到它的原型链上任何一个原型对象的属性,在访问对象的某个成员(属性或方法)时,如果这个成员未见于当前对象,那么js会在原型对象中查找它,如果还没有找到,就继续到下一级._proto_的原型对象对象中查找,直至找到。如果没有找到就会返回undifined,继承就是指构造这样的原型链。

那么就来构造原型的链接,来实现继承。举例,实现一个Japannes类,继承Person;

 1 var Japannes = function(name, nation){
 2     //调用父类构造函数,复制属性和方法
 3     Person.apply(this,arguments);
 4     this.nation = nation;
 5 };
 6 //这样行吧?直接把原型复制(注意是引用类型,值传递)
 7 Japannes.prototype = Person.prototype;
 8 
 9 //定义自己的方法
10 Japannes.prototype.getNation = function(){
11         return this.nation + "is Stupid";
12 };
13 
14 var a = new("Cangjingkong","Japan");
15 alert(a.getName());
16 alert(a.getNation);

这样就实现了基本的继承,Japannes增加了name属性和拥有了getNation方法;但是,Japannes.prototype = Person.prototype; 这是引用类型,修改Japannes同时也修改了Person,Person原型也增加了一个getNation(),这本身就是不能容忍的,且使类之间形成强耦合性,这不是我们要的效果。

常用的继承方式是Chinese.prototype = new Person();用父类的一个实例来继承,这样是完全没有问题的,但是假如父类的构造比较庞大,这样创建他的一个实例开销就会比较大,不是主要目的。

查看Extjs中Extend的源码,如下:

 1 extend : function(){
 2 
 3     // inline overrides
 4     var io = function(o){
 5         for(var m in o){
 6             this[m] = o[m];
 7         }
 8     };
 9 
10     var oc = Object.prototype.constructor;
11 
12     return function(sb, sp, overrides){
13         if(typeof sp == 'object'){
14             overrides = sp;
15             sp = sb;
16             sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);};
17 
18         }
19 
20         var F = function(){},
21             sbp,
22             spp = sp.prototype;
23          //空类实现继承
24         F.prototype = spp;
25         sbp = sb.prototype = new F();
26         sbp.constructor=sb;
27         sb.superclass=spp;
28         if(spp.constructor == oc){
29             spp.constructor=sp;
30 
31         }
32         sb.override = function(o){
33             Ext.override(sb, o);
34 
35         };
36         sbp.superclass = sbp.supr = (function(){
37             return spp;
38 
39         });
40 
41         sbp.override = io;
42         //实现重写,在原型prototype上;
43         Ext.override(sb, overrides);
44         sb.extend = function(o){return Ext.extend(sb, o);};
45         return sb;
46 
47     };
48 
49 }(),
50 
51 override : function(origclass, overrides){
52     if(overrides){
53         var p = origclass.prototype;
54         Ext.apply(p, overrides);
55         if(Ext.isIE && overrides.hasOwnProperty('toString')){
56             p.toString = overrides.toString;
57         }
58     }
59 }

其中用一个空类实现了继承,参照其写法,在这里可以这么写:

1 var F = function(){};
2 F.prototype = Person.prototype;
3 Japannes.prototype = new F();

这样既避免了污染父类Person的原型,也减少了创建父类实例(尤其是比较大的构造,例如在Extjs中常用的Web组件配置属性是比较多的)的开销,我们可以整理代码如下:

 1 var Person = function(name){
 2     this.name = name;
 3 };
 4 Person.prototype = {
 5     getName : function(){
 6         return this.name;
 7     }
 8 };
 9  
10 var Chinese = function(name, nation){
11     Person.apply(this,arguments);
12     this.nation = nation;
13 };
14 var F = function(){};
15 F.prototype = Person.prototype;
16 Chinese.prototype = new F();
17 //注意这里的设计
18 if(Chinese.prototype.constructor == Object.prototype.constructor){
19     Chinese.prototype.constructor = Chinese;
20 }
21 Chinese.prototype.getNation = function(){
22         return this.nation;
23 };
24  
25 var c = new Chinese("zhuangzhong","China");
26 alert(c.getNation());
27 alert(c.getName());

值得注意的是,在javascript中有 f.prototype.constructor = f,这样的设计,由于Person父类的prototype被覆盖(用字面量重写),这里Chinese.prototype.constructor既不是Person更不是Chinese,而是一直查询到Object(),于是这里有必要重写一下其构造函数。

最后,我们来看一个例子,ext-all-debug.js中的Ext.state.CookieProvider,它是作为Cookie的提供类来使用的:

 1 Ext.state.CookieProvider = Ext.extend(Ext.state.Provider, {
 2     constructor : function(config){
 3         Ext.state.CookieProvider.superclass.constructor.call(this);
 4         this.path = "/";
 5         this.expires = new Date(new Date().getTime()+(1000*60*60*24*7));
 6         this.domain = null;
 7         this.secure = false;
 8         Ext.apply(this, config);
 9         this.state = this.readCookies();
10     },
11     set : function(name, value){
12         ...
13     }

重写子类的constructor构造函数,在子类使用时需要使用SubClass.superclass.constructor.call(this,…);的方式,以便它的基类可以将构造函数赋值给子类,复制父类属性。

 

posted on 2013-11-05 10:03  Mr.Vangogh  阅读(767)  评论(0)    收藏  举报

导航