热门:网页模板.net视频教程JQueryMVCjsonExtJs源码示例三级联动JQuery菜单
您现在的位置:.Net中文社区>> AJAX编程>>正文内容

ExtJS 2.2继承的扩展

发布时间:2009年01月09日点击数:
返回深入学习ExtJS 2.2开发系列连载教程目录
在这一章中,我们将分析Ext的底层实现,剖析它运行机制和架构思想。Ext通过Ext静态类作为其命名空间入口,提供了继承、应用等JS框架架构的的实现,同时还提供了兼容性的统一处理。

在这一章,我们还将介绍Ext对JS原生对象的扩展及包裹,如函数,数组、字符串,定时、日期等。通过这些扩展或包裹之后的对象,可以很方便地构建Ext的开发。我们在开发也会经常用到这些函数或其实现。

①②③④⑤⑥⑦⑧⑨
本章主要内容
Extend的设计
函数的扩展
集合的使用
String的使用
定时和Date的使用
3.1 Ext静态类
Ext类是既是一个命名空间,又是一个静态类。通过这个统一的命名空间,它能能解决命名上的冲突,同时又让人觉得框架更为紧凑。作为静态类,它又提供了如最基础最底层的功能实现。对于这个全局唯一的静态Ext类,它应该提供那些功能呢?作为Ext类库的统一的入口,它又应该给出作出什么的规定呢?

从功能的角度来考虑,JS的首要问题是解决浏览器的兼容性。JS不是真正的面向对象的语言。对于开发类库的开发,代码的重用是至关重要的。考虑重用,就应该考虑到面向对象的继承等方式来实现。

从命名空间的角度来看,既然是统一的入口,那么对于其它类的命名都应该是Ext的子命名空间,如Ext.Element。这样一来,调用类的名字就很多,可以通过把常用的功能以缩写的形式注册在Ext的命名空间中。如Ext.decode() 即是 Ext.util.JSON.decode函数的缩写。

下面我们就把Ext类的方法和属性进行归类,便于记忆和查找。

兼容性

Ext类不可能提供浏览器的兼容具体的处理。因为兼容处理只能落实具体的方法中。它提供了一组属于用来判断用户使用的浏览器、操作系统等,在需要兼容处理的地方只要直接调用这些属于就可以进行判断再来进行相关的兼容处理。

isAirisBorderBoxisGeckoisIEisIE7isLinuxisMacisOperaisSafariisStrictisWindows这些属性是Ext类提供的兼容方面的属性。这些属性大部分是从navigator.userAgent来获取到的。

重用性

Ext类中提供二组关于代码重用的方法。

第一组是applyapplyIf函数,它实现把对象内部的方法或属于直接复制到另外一个对象。这是最基础的继承方式。

第二组是extendoverride函数,这一组是高级继承方式,它不仅仅是把一个函数的方法和属性复制到另外一个函数的原型中去,它还要建立其两者之间的父子链的关系。

组件入口

Ext类中提供了组件的入口getCmp(id)函数。

每个组件都有id属性,只要定义时给定其值,之后在任何地方都通过Ext.getCmp(id)来获得该对象的引用。这为全局管理组件提供了方便。其是Ext.ComponentMgr.get的简化。这一部分内部在后面的章节会详细地讲解。

元素入口

所有Js类库的最终目的都是对于Dom元素进行操作。ExtDom元素进行封装为Element元素。但是在Ext类也提供了很多这方面的方法。

1、事件处理。Ext类提供了addBehaviorsonReady两个事件处理函数。一个是监听DomReady,一个是给所指定的元素注册事件监听。

2、获取元素。flyget是根据参数获取元素的两种不种的处理方式。getBodygetDoc则是获到指定Dom的元素。而getDom是从封装的Element元素中获取其原始的Dom元素。

3、查询Dom树。queryselect是两个CSS语法兼容的Dom query方法。通过这两个方法能方便地用Dom树中找到元素。

4、删除元素:removeNodedestroy是为了删除元素,释放内存而给出的。destroy还能删除组件。

实用方法

Ext类提供如下四类的实用方法:

1type类型。Jstypeof只能判断最原始的类型,如Array是返回是Function,而new Array()则是数组。对于dom元素不能深入去判断其节点类型。Ext.type则指代了更为精准的判断。它还提供了isArrayisDate等类型判断函数。

 2、命名空间。Ext.namespace()函数能动态生成指定的命名空间。

3、格式转换。Ext类提供了decodeencode 方法实现stringjson对象之间的相互转换。还提供了urlDecodeurlEncode方法来进行查询字符串和JS对象进行的相互转换。在3.4节中分析。

4、其它:在new RegExp时,对于正则的字符串中的特殊符号要进行特殊的处理,Ext.escapeRe 就是进行这样的操作。 Ext.each是集合的元素都进行相类似的操作,如Fn的操作。这是很常用的函数。

             3-1      Ext类的功能归总表

在上表中,我们把Ext类提供的功能分成了五部分。下面小节就是对这几部分进行展开来进行分析。兼容性的部分的代码简单,下面就不展开来,读者可以能对着Ext类的源码自行分析。组件入口部分,其内容众多且复杂,后面的几个章节会对其进行详细的分析。

3.1.1 代码重用
类库的代码重用,是评价一个框架架构好坏的标准之一。关于代码的重用,面向对象中的继承方式是目前最佳的解决方式。但是JS不是标准的面向对象语言。它可以通过函数的prototype的方式链来实现继承(如果不明白其原理,可以去阅读Ajax基础教程或JavaScript权威教程),但是这种继承方式并没有实现父子类之间的联接关系。同时这仅仅是函数的继承。对于建立在JS上运行的富客户端的Ext来说。这种继承方式有点捉襟见肘。

一般的JS类库都提供了二组操作。一组是对象之间的继承。一个对象能得到其父对象的属性和方法。另外一组是函数之间的继承(即类的继承),都在函数的prototype方式链基础之上提供更强大的继承功能。

对象的继承和函数的继承是不一样。对象的继承就是直接的属性或方法的复制(其实是引用)。而函数是构建对象的构建器。也就是一个函数可以构建无数个该函数类型的对象。那么这个构建之后的每一个对象都拥有这个构建函数的prototype中的所有方法和属性。

对象的继承
本来按道理来讲,对象的继承方式应该扩展在JS的对象中。让每个对象都有这个功能。Prototype 1.3是这样做的,但是由于大部分时候,该方法是用不着的。而且这样的做法会所有的对象的反射造成影响,如原生对象(Array)就不需要这个方法。如果扩展了,就不采用for property in obj这样的反射的方式来访问其内部的元素。所以prototype1.4及其他的类库都不会直接为object提供extend的方法。

在Ext中,我们可以采用Ext.apply的这个静态方法来实现。这个方法在Ext类库的开始位置,也说明了对象的继承是很重要的。其文件中接下来的代码片断中Ext.apply的应用可以看作是Ext.apply的最佳应用。它对外面屏蔽了一些变量命名的引用,同时又把那些需要的实用函数复制到外部的Ext命名空间中。

Ext.apply函数功能强大,但是实质原理是很简单的,通过对象的反射,把其所有的方法和属于都复制到另外一个对象内部。 Ext.apply的源码
Ext.apply = function(o, c, defaults){
     if(defaults){  Ext.apply(o, defaults);   }
     if(o && c && typeof c == 'object'){
         for(var p in c){ o[p] = c[p];  }
     }

     return o;    

从上面的源码可能看出Ext.apply提供了一个defaults的参数,也就是说在继承之前,我们可以为子对象指定一些方法或属性,如是子对象为空对象({}),可以说是初始化子对象。不过初始化子对象,完全可以在第一个参数通过{}的方式来指定。一般来说defaults使用的情况不多。

在应用时的大部分情况,我们并不把这个函数当作继承来看待,我们把它当作应用来看看待,把父对象的方法和属性统一应用到子对象中去。可以看作一种很纯粹的对象之间的拷贝活动。

在应用中有时候我们并不要覆盖子对象中原有同名的方法或属性,只是增加那些没有的方法或属性。通过Ext.apply就实现不了。为了解决这个问题,Ext.applyIf在apply的基础之上通过增加了typeof o[p] == "undefined"代码来判断其子对象是否有同名的属性来实现的。

在对象之间拷贝属性或方法,这两个方法满足绝大部分的需要。但是有的时候,我们会想着把它们两者统一起来该多好。还有Ext.apply只支持三个参数,假如我偿想从很多对象复制中方法和属性呢?那不是要多次调用这个函数?这个实现也不难。请读者对着mootools的$extend和Jquery.extend代码自行分析。

类的继承
在javaScript中,面向对象的类是通过函数来模拟的。函数的prototype中的方法和属性可以看作是类的实例方法和属性。函数的的方法和属性可以看作是类的静态(类)方法或属性。对于函数的继承,当然也就是类的继承。

函数的继承和对象的继承差别其实也不是很大。对象的继承是复制到对象的内部,而函数的继承则是把数据复制到函数的原型中。Ext.override方法就是完成这项工作。它只是简单的复制,没有理会继承者与被继承者之间的关系。还有一些问题是值得考虑的,如果没有给定继承者,是不是要构建一个继承者,让其继承被继承者的所有功能呢?继承者是不是也应该有继承的能力呢(为其提供继承的方法)?Ext.extend解决了这样问题,该函数是Ext类库的基石。

Ext.extend函数分析
Ext.extend函数仅仅当作extend的功能来看待是有点以偏概全的,我们完全可以把看作能继承的构建器。它的功能有点像prototype1.6中类的构建器(Class.create)。在Ext中,我们可以把Ext.extend看作是组件的构建器。如果没有指定子类,它会在继承的同时生成子类并返回。Ext组件树的应用就是完全建立在extend函数的基础之上。而Ext.extend又可以在继承的过程中构建一个新组件并返回。当作组件的构建器一点也不过份。

通过Ext.extend来实现的组件,我们可以通过类的继承链来访问其父类的方法或构建函数,如Office.CompanyPanel.superclass.constructor.call(this, this.meta);在通过extend实现的组件,其在类的关系上已经形成了链条的关系。我们可以通过这个关系访问到其父类。 Ext.extend的源码:
点击展开()                                                            ⑩

在上面代码中⑩,我们可以发现这个函数是立即运行的函数。也就是说Ext.extend实际上是②处的函数。那么它为什么要这样做呢?完全可以把①处的两个变量放在②处的函数内部。

因为①处相对每个继承的应用,它们都是不变的。放在函数内部的话,那次每次extend,都要生成这两个变量,用完之后销毁,影响性能。也可以把它们放在Ext的命名变量中,这样做的不好之处在于外部可以引用这两个变量,而我们想这两个变量只能在Ext.extend的内部函数中引用。这里的用法就是采用了函数的闭包特性。提高了效率,又保证其变量的私有性。

上面代码的③是对省略了子类参数的处理。在这种情况下,Ext.extend就要构建子类。如果在构建的对象参数指定了构建这个子类的函数就采用这个函数做为子类的构建器。如果没有的函数,就采用默认的处理,它的默认的处理是把参数传入其父类的构建函数中并运行它。

④处是实现子类原型继承父类原型中的数据。⑤实现了类链的形式。④处第二句和⑥处是指定子类和父类原型的constructor方法,让其指自身的构建函数。⑥处第二句及⑦和⑨处分别为子类或其原型增加override或extend的函数功能。⑧处就是把overrides中的函数或属性全都复制到子类的原型中。

使用Ext.extend是很简单的,对于不需要在构建方法中直接处理一些功能话,我们完全可能通过如下的方法来生成一个即有方法又有属性的继承的类:

var myclass=Ext.extend(spclass,{/ 一些增加s方法和属性});

在Ext的组件中,子类的构建方法中实现的功能一般都在Compoent类中的模板方法initComponent中实现。也就是说一般都是采用上面方面来生成继承的子类。

对于采用子类的自实现的构建方法,相对来说是有点麻烦,如下:
var myclass=function(config){

.. .. ..

myclass.superclass.constructor.call(this, this.meta);//这里是把参数传到父类中构建方法中

… …

}
Ext.extend(myclass,supclass,{//一些增加的方法或属性});

有的时候,还是要可能要采用这种方法来进行继承的,特别是那些不是组件的继承方式,又要构建方法中自定义实现自有的功能。Ext.extend这两种应用,我们在办公系统的扩展组件都会用到。在第二章的leftMenu组件中,我们就是通过它第二种应用的方法来实现其组件继承于Panel组件。

Ext.extend函数的扩展
在使用Ext.extend的过程中,很多人会对很长很长的类链形式的父类中的方法的访问感觉不满。因为在Java中,我们可能通过this.supper来访问其父类的同名方法的。我们能不能有一个好的方法来解决这个问题,同时又保证Ext.extend兼容以前的类链形式的访问。

在代码3.2清单中⑤处,通过sb.superclass=spp为其静态的类注册superclass属性用来访问其父类,那么在其后加上sbp.superclass=spp;为其实例注册superclass属性能不能达上我们的要求呢?

表面上是可以的。但是细一想,如何是祖先,父亲,孩子三重继承的话,父亲通过调用this.superclass时,其得到不是祖先,而是自身。因为this是指向孩子。那怎么办呢?有没有在继承的时候就保存其对其自身的父类的引用呢?

也就是说在调用this.superclass的时候,它的superclass要改变其引用,这个改变是根据其所自身所在类而作判断的。但是superclass只是属性,调用时肯定是改不了的,如果我把this.supercalss的调用转换成this.supercalss(),那么在调用时就是运行supercalss(),这个方法在运行时找到其正确的父类。

那么现在的任务是如何实现这个supercalss()函数?要找到这个子类的父类的话,那么在继承的时候,就先在这个子类的什么地方来保存其父类的引用。什么地方呢?因为this的方式是对象方式的访问,而不是类方式的访问,保存在类中有点麻烦。

既然supercalss()是对象方式的访问,可以不可以保存在supercalss()的静态属性中呢?能这样的话,supercalss()的内部实现只要根据不同的子类而引用到这个静态属性就可以了。这里还有一个问题就是可以保证多重关系的孩子的同名静态属性不覆盖其父亲的同名静态属性?这就得采用闭包引用的原理了。

我们可以先把代码清单3.2中的return语句之前加上sbp.superclass=superclass(spp);这样每个子类的原型中都有一个自已superclass的方法(返回的是函数)。这个方法是要传入其父类的原型做为参数并将其保存在返回的函数的静态属性中。

接下来要做是根据调用这个函数的的不同类来判断啦,这个可以通过arguments.callee来引用其函数对象本身,尽管是同名的superclass,但是对于不同的层次的子类,其函数实质是完全不同的函数对象。其superclass实现如下:
var superclass=function(sp){
      var s=function(){ return arguments.callee.superclass; }
       s.superclass=sp;
       return s;};

上面的代码可以看出每次不同层次的子类,都是返回不同的s函数,它的函数体内容是一样的。但是其superclass是不同的。函数体中的arguments.callee就是根据this.superclass()调用来判断其是指向那个s函数。(实质上是没有判断的,因为this.superclass()就是找到当前对象的s的引用)。这一段代码放在Ext.extend()函数起始位置就可以了。Ext.extend扩展测试 
点击展开示例;
Animal.prototype.getAge = function() {
     alert(this.name+"'age is "+this.age);
}
;
Animal.prototype.getName = function() {
     alert("this is the animal Name!");
}
;
var Cat = Ext.extend(Animal, {
    name : 'cat',
    age : 5,
    constructor : function() {
        alert("this is Cat constructor");
       this.superclass().constructor(arguments);
    }
,
    getAge : function() {
       alert(this.name + "'age is " + this.age);
       this.superclass().getAge();      
    }

}
);
var homeCat = function() {
     alert("this is homeCat constructor");
    this.superclass().constructor(arguments);
}
;
Ext.extend(homeCat, Cat, {
    name : 'homeCat',
    age : 3,
    getAge : function() {        
       this.superclass().getAge();      
    }

}
);
var myCat = new homeCat();
myCat.getAge();

这是建立在上面扩展之后的例子,通过new homeCat(),它会依次弹出homeCat,Cat,Animal构建函数的alert的内容。调用myCat.getAge(),它也会依次弹出homeCat,Cat,Animal各自的getAge中alert的内容。尽管都是通过this.superclass来调用,却还是保证其继承的关系。

这样的继承和Ext的继承是有点区别的,Ext组件树的继承都是采用如this.superclass().getXX.apply(this,arguments)的方式,每个继承链的联接都是通过apply来改变其作用域,把其所有父类中的实现方法中的this都指向当前运行的实例。

这也就是所有继承父类的this都是针对当前运行的实例进行操作,通过这样的实现,能在父类中调用子类的函数,也就是模板方法的应用。而上面扩展的继承方式则有点不同,它的this只能是指向当前类中的方法或属性。如果当前类没有的话,就会从其父类或祖先类去查找。子类对于父类中属性或方法进行了修改,这个并不会影响父类其它方法对这个属性或方法进行的调用或操作。也就是this指向是纯粹的该方法所在类的类对象。

在应用中我们可以分情况来使用这两种继承方式,在实现继承链的时候,采用Ext的类链的继承方式是易于理解,同时也能更好的架构性。在那些不需要改变父类函数作用域的时候,就可以通过this.supperclass()的方法来调用父类的实现方法。这样代码更简单。使用起来也方便。

说到使用方便,其它很多时候,在当前函数中是调用父类的同名函数。如果采用this.supperclass()之后还得跟上同名的函数。很多类中都可能通过this.parent()来实现,如mootools的继承中。这里我们作一个改动。通过this.superclass(this //同名函数的参数)来实现。这样也能简化一点工作。

这个实现只要在前面的supperclass函数中进行是否有参数的判断就可以实现,有的话就找到同名函数,传入指名的实参,没有的话,就返回父类对象。我们只要把上面的supperclass修改成如下代码:
点击展开示例;

这样的话,我们就可能通过this.superclass(this)来调用父类的同名方法。本函数通过了Animal,Cat,homeCat三层继承的测试,具体代码见光盘的源码。从在测试中是可以兼容的原始的Ext的框架的,但是没有经过周密的测试。如果要把这段代码放在Ext中采用。最好还是进行完整地测试。

本站热点业务

更多模板/案例展示

关于我们 | 联系我们 | 团队日志 | 网站地图 | 网站合作