从Java的角度理解Ext的extend |
本文标签:Java 在Java中,我们在实现继承的时候存在下面几个事实: 1, 准备两个类,他们用extends关键字链接起来 2, 如果超类没有默认构造函数,需要在子类构造函数中显式的super并传参,如果都是默认构造函数也可以super,不super虚拟机是自动的 3, 子类可追加,覆盖,重载方法,子类可以有自己的私有属性,他们在子类构造函数中被构造 4, 字段是数据,方法在代码区,和类建立方法表,同一个类的对象有自己的数据但是共享方法代码
比如有两个类,Plane和Space,Plane表示平面,Space表示空间,Space是Plane的子类,在java中
那么在js中也一样,区别是代码要放到构造函数(可以理解为Java中的类)的原型上,原型是放置方法和不变属性的理想场所,原型是一个对象,它和普通对象唯一不同的就是他有一个constructor属性指向它所依附的构造器,在java中子类查找属性和方法是通过虚拟机来完成,但是在js中需要通过原型链来完成 。也就是说继承关系对程序员是不透明的,需要了解这个原型机制,原型机制上存在两条链,一是原型链,二是构造函数链 。 仿照上面java的代码,我们可以完成js的写法,如下:
JS中函数的this指函数的调用者,不管是java还是js,this都可理解为新分配的那段容纳对象的内存 。在java 中通过Space extends Plane,虚拟机就维护好了他们的继承关系以完成继承关系的自动查找,但是在js中需要我们手动的处理,在这个时候Space是调用不到XY这个方法的,因为他们没有在原型链上 。我们可以开发一个函数来模拟java的关键字extends,比如这个函数叫做extend,通过执行extend(Plane,Space)完成原型链的组装 。 那么extend怎么实现呢?首先要明白原型链,子类和父类在原型链上的关系是Space.prototype._proto_ == Plane.prototype,如果你理解不了,那就看String这个类吧,String.prototype._proto_ == Object.prototype,即String的原型会链接到Object的原型上,链接是通过_proto_这个属性来完成的 。_proto_是一个只读的属性,只能通过构造函数写入,所以String是Object的子类 。 现在Plane的prototype._proto_ 等于Object,Space的prototype._proto_也等于Object,我们要在extend函数变换这个关系,即完成Space.prototype._proto_ == Plane.prototype,我们知道一个对象的_proto_要指向某个构造函数的原型,需要让这个对象由那个构造函数构造,那么我们只需要让Space.prototype = new Plane()就可以了,这个时候Space.prototype._proto_ == Plane.prototype,而不再指向Object,原型还有一个属性constructor指向原型所在的构造器,由于Space.prototype刚被Plane创建出来,还没有这个属性,我们要手动赋值上去,代码是Space.prototype. constructor = Space 。这样extend的责任就完成了 。 但是这里有两个问题: 1, 由于Space的原型在extend中被替换了,那么它原有的方法就没有了 。 2, Space的原型是Plane构造的,虽然做到了Space.prototype._proto_ == Plane.prototype,但是Plane也在原型上写入了x,y这两个垃圾数据,他们都是undefined,没有意义,所以要手动删除掉,这样extend这个方法就不能通用了 。 首先解决第一个问题,我们要变化一点思路,利用js中函数也是数据的特性,我们把Space的那些方法拷贝到一个对象中,比如
把这个sbm也传递给extend,extend在替换完原型后将sbm上的所有方法复制到Space的原型上即可,sbm是一个对象直接量,用json语法 。现在的extend就变为了三个参数,即extend(sb,sp,sbm),sb是子类,sp是超类,sbm是子类要放到原型上的方法 。 对于第二个问题,本质原因是Plane这个函数要完成一些数据初始化,它是超类,我们不能控制,我们只关心Plane的原型而不关心它构造什么数据,所以我们可以把它的原型单独拿出来,再定义一个干净的函数,这个函数不做任何事,将这个干净函数的原型设置为Plane的原型,再用这个干净函数构造一个对象,这样出来的对象就是是干净的,也完成了_proto_指向了Plane.prototype,完美!有了这两个方法,我们就可以开始实现这个extend,代码如下:
那么完成Space继承Plane的代码如下:
OK,到了这里,我们基本上就完成任务了,完全从java的方向搞定的 。我们现在利用js的特性来优化,让使用extend更加简单 。 我们说在java中必须写两个类,每个类都写自己的字段 ,构造函数,方法等,在我们实现的extend函数中也确实把子类,父类都传递了进来,但是我们多了一个参数,那就是子类的方法集合即sbm,第一个参数sb本身也是函数,那是不是可以将这个函数也放进sbm传进来呢?这样extend就变为两个参数,即extend(sp,sbm),现在extend返回一个函数,返回的这个函数就是sp的子类,这是完全可行的,我们叫做extend2吧 。
我们说要把子类的构造函数放到sbm上,放上去的key叫做constructor,就表示构造器,js中每一个对象都一个constructor属性,它指向构造了这个对象构造函数 。sbm本来是个Object对象,它的constructor就指向Object,这个constructor是在sbm关联的那个原型上的,现在我们在sbm上设置某个子类的构造函数,这个时候表示sbm有个自己的constructor 。 现在我们在extend2中要做的事情就是提取出构造函数,然后还原为三个参数去调用之前的extend,在java中我们的子类是可以不用构造器的,只要父类也有默认的构造器,那么在这里一样,sbm可能不包含constructor,那么我们需要做一个函数,它调用父类的构造函数,在java中这种情况过程是自动的 。所以当sbm.constructor为Object的时候表示sbm没有指定构造函数,这个时候将
即调用父类构造函数 。这样将sb,sp,sbm传递给extend就可以了 。 这个时候我们新的继承语法如下:
和prototype.js和mootolls的实现比较,大同小异
到了这里其实已经差不多了,但是细心的读者会发现,我们在extend中会把sbm的所有属性拷贝到子类的原型上,这里岂不是就要把constructor也拷贝到原型上?如果sbm包含了这个constructor其实就无所谓,因为子类的原型的constructor本来就是需要指向这个构造函数的,但是sbm上没有constructor那岂不是要把Object拷贝到子类原型上,答案是不会的,我们在拷贝的时候用的for in循环是迭代不出默认的那个constructor的 。 现在我们来看看Ext.extend,应该完全没有问题了 。我们用了两个方法extend,extend2,Ext把它合并为了一个方法Ext.extend,所以它会判断传进来的参数然后进行变换,这样Ext.extend就支持两个参数和三个参数进行调用 。对于前面用到拷贝属性,Ext做了一个工具函数叫做Ext.apply,对于将一个对象的属性拷贝到一个类的原型上,Ext做了一个工具类叫做Ext.override 。
现在使用Ext的extend来实现我们之前的继承代码就如下
现在我们来分析一下Ext中的继承重头戏Observable,它位于Ext.util这个包下,它的意思即是观察者,使用观察者模式,EDA模式,UI组件就利用这种基于观察和事件的机制进行通信和渲染 。 所有的UI组件都继承这个类,我们看看它的构造函数
这个构造函数不需要参数,在java中,这种父类的构造可以自动的调用默认构造函数,但是这里要注意,if(me.listeners)依赖了子类的构造行为,这在面向对象原则中似乎是一个禁忌,但是如果一个继承体现完全由一个团队维护,他们同时制定继承规则和继承规范,这也无可厚非,这里的listeners可以在子类中不提供,可以让构造出来的对象自己调用on方法来添加监听器,同理这里的events,如果子类没构造会被赋值为一个空对象 。那么这个Observable构造器做了两个事,一个是看子类是否在对象上放了监听器,如果放了,就调用对象的on方法进行事件和监听的绑定,二是看子类是否在对象上放置了events,如果没有就把对象的events属性设置为一个空对象 。也就是说子类是完全可以不做任何事的,子类只负责自己的数据构造和行为覆盖或追加,events在和监听器绑定之后就是一个Ext.util.Event对象的容器,见这行代码: me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);也就是说,在Ext中,一个活生生的能够响应事件的对象有一个Event容器,它保存了这个对象可以响应什么事件以及事件被触发后被调用的监听器 。 Observable原型上放置的方法都是子类继承的方法,子类的对象就可以在运行时调用这些方法,如下:
原型上放置了一个静态变量和一些方法,这些方法都是和事件以及监听有关,注意Observable的原型是一个新的对象直接量,它的constructor属性肯定指向的是Object,不是指向的Observable,这岂不是存在bug,我通过代码检测发现alert(Ext.util.Observable.prototype.constructor == Ext.util.Observable);的结果确实又是true,怎么回事呢?答案就在Ext.extend的那段防御性代码,大家回过去看看吧! 现在我们写一个继承Obervable的类,不过不是UI组件,而是一个领域模型,比如论坛帖子,它在被修改之后会跑出一个被修改的事件,监听器捕获这个事件将修改保存到数据库中,代码如下:
如果事件设置和监听绑定直接在子类完成,那么就不必显式调超类构造函数
原文链接:http://www.blogjava.net/Adley/archive/2011/12/01/365300.html |