`

Ext extend

阅读更多


第一章   必须理解Ext.extend 函 数

显然了,从函数名就可以看出来,这个函数是整个Ext 类 库的基石,之一。

笔者初看这个函数,感觉比较迷糊,只知道它完成了继承的功能,但是里面到底做了什么操作,并不太好理 解。

网络上有很多帖子和文章来解析这个函数,但笔者感觉并不是很到位。不知道是因为这些帖子的作者没有能完 全理解这个函数还是因为表达得不够清晰。

下面笔者就自己的理解来分析一下这个函数。

必须的 前置概念有三个:prototypeconstructor 、 “闭包”

没有这 三个概念的请务必先看第123 段代码,很了解的直接看第4 段代码就可以了。

1 prototype

   RectAngle=function (width,height){

      this.width=width;

      this.height=height;

   }

   RectAngle.prototype.area=function(){

      return this.width*this.height;

   }

   这段代码似曾相识吧?来自《JavaScript 权威指 南》。功能很简单的啦,定义个“矩形”的构造函数,有长和宽两个参数。

   然后在RectAngleprototype 里面增加一个计算面积的函数area.

   这样每次在var rect=new RectAngle() 的时候,就可以对rect 对象调用area() 函数了,因为rect 对象从RectAngleprototype 里面继承了area() 函数。

   这就是“JavaScript 基于原型继承”的简单理解。

2 constructor

   根据《JavaScript 权威指南》上面的解释,每个函数 都有一个prototype 属性,构造函数也是函数,所以也有prototype 属性。prototype 属性在定义 函数的时候会自动创建并初始化. 也就是说, 在 写下RectAngle=function(widht,height){//...} 的时候,RectAngleprototype 属性就已经 被创建了, 这个时候prototype 里面 只有一个属性, 它就是constructor( 构 造器), 这个constructor 指回了RectAngle 函数本身。这样就形成了一个圈一样的链条, 可 以实验一下这种调用:

   RectAngle.prototype.constructor.prototype.constructor... 这个调用是比较变态的咯,如果你能看懂,你肯定琢磨过这个问 题,呵呵。笔者也是琢磨了比较长的时间才明白其中的含义的。

当然,不明白这种变态写法也没关系的,毕竟每个哪个变态的人会在实际应用的时候写这种东西。

   言归正传,对于每个RectAngle 的实例来说, 例如var rect=new RectAngle(10,10) rect.prototype 会指向构造函数RectAngleprototype ,也就是说所有的实例都会共享同一份RectAngle.prototype

   如此,就不需要分配那么多内存给每个实例来存储prototype 属 性了。

3 、“闭 包”( 代码来自《JavaScript 权威 指南》)

   RectAngle=function(width,height){

        this.getWidth=function(){return width};

        this.getHeight=function(){return height};

   }

   RectAngle.prototype.area=function(){

      return this.getWidth()*this.getHeight();

   }

   发现了吧? 这段代码和第1 段的构造函数是不同的.RectAngle.prototype.area 这个函数也可以看出来,除了RectAngle 构造函数内部,外部函数无法直接访问RectAnglewidthheight 属性,只能通过执行getWidth()getHeight() 方法来 获得这两个属性的值。

   《指南》上面说,第一个发现这种写法的人是Douglas Crockford ,呵呵,真是个变态的家伙,这都能想出来!无语啊,人和人是有差距的。(笔者的名言)

   有了这种写法,就可以动态构建出功能强大的代码了,这种写法的用处是比较多的,例如像缓存调用变量、改变命名空间、 定义私有属性等。依次来解释一下这三个用处:

   ⑴定义私有属性:从上面的代码可以看出来,外部函数是没有办法直接引用widthheight 这两个属性的,比如var rect=new RectAngle(widht,height);rect.weidth??

   这么写就不行了。所以,通过RectAngle 构造器中this.getWidth() 方法就模拟出了一个私有的变量( 因 为JavaScript 没有private 这 个说法,所以只能叫模拟哦)

   ⑵改变命名空间:

   例如把上面的代码写成这样:

   RectAngle=function(width,height){

      getWidth:function(){

          var haha=function(){

              return width;

          }

          return haha;

      },

      getHeight:function(){

          var haha=function(){

              return height;

          }

          return haha;

      }

   }

   同样是可以运行的,看出来没有,两个get 函数里面实际上用 了同样名称的方法haha() ,但是没有关系,它们的命名空间是不同的,一个处于getWidth 的作用域,一个处于getHeight 的 作用域。当然在外部调用getWidth() 方法的时候,实际运行的是里面对应的haha() 方法。

   ⑶缓存变量:

   Java 或者C++ 的 作用域概念类似,一个方法中局部变量(方法的参数也可以看成是局部变量的一种),在方法运行完之后就会实效并释放内存。

   例如var rect=new RectAngle(width,height); 按理说,在构造函数执行完毕之后,widthheight 这两个变量就应该释放内存了,但是通过类似这种this.getWidth=function(){return width} 的定义,widthheight 变 量并不释放内存,否则在外部调用getWidht() 的时候,就无法返回对应对象的width 值了。

   (“闭包”是稍微复杂的概念,在很多的脚本语言里面都有这个特性,JAVA 中 目前是没有这个概念,据说JAVA7 将会添加“闭包”特性。但是笔者认为,作为一种重量级的语言, 并不是什么特性都要有,像“闭包”这样的东西,在重量级语言里面,稍有不慎“内存泄露”起来是so easy 的! 如果写得再变态一点,很多局部变量都可以“逃出作用域”,变成内存孤岛(没有函数可以释放它,只能看着它干瞪眼)。

4 、好 了,有了上面的简单解释,可以来分析Extextend 这 个函数了。

   首先还是把《指南》里面的继承的例子说一下,以便于理解(你很熟悉?跳过吧。)

   RectAngle=function(w,h){

      this.w=w;

      this.h=h;

   }

   RectAngle.prototype.area=function(){

      return this.w*this.h;

   }

   写个子类来继承RectAngle ,这个子类叫做有颜色的矩 形ColoredRectAngle ,多一个color 属 性

   ColoredRectAngle=function(color,w,h){

      // 首先调用父类的构造函数来拷贝wh 属性,这样的话,ColoredRectAngle 也 就有了wh 两个属性

      // 为什么不直接this.w=w;this.h=h? 倒,这样 的话,你还用继承干嘛呀?

      RectAngle.call(this,w,h);

      this.c=color;

   }

   上面已经把wh 属 性拷贝到子类中来了,父类的prototype 里面还有个area 方 法也得想办法拷贝进来,注意了,这是精彩的部分,不能错过哦。

   ColoredRectAngle.prototype=new RectAngle();// 这个写 法其实包含了很多内容哦,我们把它拆开来写会更好理解

   var rect=new RectAngle();

   ColoredRectAngle.prototype=rect;// 怎么样,含义是一样的吧?

   好,开始分析这两句话。rectRectAngle 的实例(废话,它是由RectAngle 构 造函数构造出来的,当然是它的实例了!),但是

   在构造rect 的时候,没有传参数给它,这样的话在rect 这个对象里面wh 这两个属性就是null( 显然必须的)

   既然rectRectAngle 的 实例,那么它的prototype 会指向RectAngle.prototype , 所以rect 对象会拥有area() 方法。

   另外,rect.prototype.constructor 指 向的是RectAngle 这个构造函数(显然必须的)。

   好,现在ColoredRectAngle.prototype=rect , 这一操作有三个问题,第一,rectwh 被放到ColoredRectAngle.prototype 里 面来了,第二,rect.prototype.area() 这个方法也到了ColoredRectAngle.prototype 里面了,当然,完整的访问area() 方法路径应该是ColoredRectAngle.prototype.prototype.area() , 但是因为JavaScript 的自动查找机制,放在prototype 里 面的属性会被自动找出来(加入从对象的直接属性里面找不到的话。)这样就没有必要写完整的访问路径了,直接写ColoredRectAngle.area() 就 可以找到area() 了,看上去就好像ColoredRectAngle 也 拥有了area() 方法。

   值得注意的一点是,在执行RectAngle.call(this,w,h); 这 一步的时候我们已经把wh 两个属性拷贝到ColoredRectAngle 里面了,这里我们不再需要rect 里 面这两个值为nullwh

   所以,直接把它们删除了事,免得浪费内存。

Delete ColoredRectAngle.prototype.w;

delete ColoredRectAngle.prototype.h;

   OK ,到了这一步,看起来模拟继承的操作就算大功告成了,父类RectAnglewh 属性通过RectAngle.call(this,w,h) 拷 贝进来了, 父类prototype 里面的方 法也拷贝进来了,没用的废物(rect 里面,也就是ColoredRectAngle.prototype 里 面,值为nullwh) 也剔除掉了。

   看上去世界一片和谐。但是... 还有一个暗藏的问题,请看: 第三:这个时候ColoredRectAngle 类的constructor 指 向错了。

   本来,如果没有ColoredRectAngle.prototype=rect 这 步操作,ColoredRectAngle.prototype 就是JavaScript 自动创建出来的那个prototype , 这个prototype 有个constructor , 指向了ColoredRectAngle 构造函数自己.

但是,现在ColoredRectAngle.prototype=rect , 如果现在来访问ColoredRectAngle.prototype.constructor , 那么,根据自动查找机制,会找到rect.prototype.constructor, 但这个constructor 指向的是父类RectAngle 构 造函数,这个就不符合prototype 的游戏规则了。因为,如果此时

var coloredRectAngle=new ColoredRectAngle('red',10,10) alert(coloredRectAngle.constructor);

得到的是父亲RectAngle 的构造函数,从 面向对象的观点看,这个结果是可以理解的,毕竟,子类对象也可以看成是父类对象。

   但是,这样的话对于ColoredRectAngle 的实例 来说,就不能确切地知道它的constructorColoredRectAngle 了。

   所以,需要手动地把ColoredRectAngle.prototype.constructor 设 置回来。于是有了这一步:ColoredRectAngle.prototype.constructor=ColoredRectAngle.

   OK ,看完以上内容,如果你的意识仍然清醒,那就恭喜你了。否则,再仔细看看吧。

  

 

   正式开始分析Ext.js 里面Ext 这个全局对象的extend 方法。

   完整的代码清单如下:

   extend : function(){

            // inline overrides

            var io = function(o){

                for(var m in o){

                    this[m] = o[m];

                }

            };

            var oc = Object.prototype.constructor;

 

            return function(sb, sp, overrides){

                if(typeof sp == 'object'){

                    overrides = sp;

                    sp = sb;

                    sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);};

                }

                var F = function(){}, sbp, spp = sp.prototype;

                F.prototype = spp;

                sbp = sb.prototype = new F();

                sbp.constructor=sb;

                sb.superclass=spp;

                if(spp.constructor == oc){

                    spp.constructor=sp;

                }

                sb.override = function(o){

                    Ext.override(sb, o);

                };

                sbp.override = io;

                Ext.override(sb, overrides);

                sb.extend = function(o){Ext.extend(sb, o);};

                return sb;

            };

        }()

   首先,总体上看它是一个自执行函数,当Ext.js 这个文件 被浏览器加载的时候最外层的无参function 就被执行。这个无参的function 返回了一个有三个参数的function(sb,sp.overrides) 。 还记得上面的“闭包”吗?这种使用方式还是相当有创意的,Ext 库里面存在大量类似的闭包写法。

   var io = function(o){

        for(var m in o){

            this[m] = o[m];

        }

   };

   这一段就不用解释了,是一个用来拷贝属性的普通函数。

  

   var oc = Object.prototype.constructor; 这句定义了一个变量oc , 它的值是Object 这个根类的constructor , 大家可以把它alert 出来看,

   它是这样的

function Object(){

         [native code]

   }

   显然,JavaScript 类库并不希望我们看到这个函数里 面的实现,但是我们知道alert 出来的这个东西就是JavaScript 根 类Object 的构造函数。

  

   来分析这个带有三个参数的闭包函数,

    if(typeof sp == 'object'){

        overrides = sp;

        sp = sb;

        sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);};

    }

    单是这个if 判断当时就让笔者郁闷了好久,呵呵,人和人真的 是有差距的啊!

    if(typeof sp == 'object') 这个判断是干嘛的呢?呵呵,它是用来判断你传递进来参数的个数 的。例如Ext.Panel = Ext.extend(Ext.Container, {...});

    Ext 类库里面基本都是传两个参数给extend 方法,此时,这个if 判断就要起作用啦。还不明白?硬是要说破啊。因为如果只传两个参数的话,在function(sb,sp,overrides) 看来

    第二个参数sp 不就是个“object ”麽?

    sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);};

    这一句用来决定子类使用什么形式的构造函数,如果overrides 里 面有个constructor 属性,就用overridesconstructor 当作子类的构造函数。否则,创建个新的function 出 来,里面包含一句话,就是"sp.apply(this, arguments);" ,这个又是闭包的一个应用哦,在退出extend 方法之后 并没有释放局部变量sp 的内存空间。

这样的话,每次new 一 个子类的时候,第一句执行的就是sp.apply(this,arguments); 这个方法与 《指南》里面RectAngle.call(this,w,h) 完成的功能是一样的。就是把arguments 全部拷贝到子类中去。

好了,属性拷贝完成之后就要拷贝父类prototype 里面的方法了。来看看Ext 又有什么 精彩的写法:

     var F = function(){}, sbp, spp = sp.prototype;

     F.prototype = spp;

     sbp = sb.prototype = new F();

     sbp.constructor=sb;

     这几句要连起来看哦。

      按照前面《指南》里面的写法的话,应该是这样的:

     第一步:把子类的prototype 赋值为父类的实例对象。sbp=sb.prototype=new sp();

     第二步:删除不要的废属性,因为前面的if 判断里面sp.apply(this,arguments) 已经完成了属性的拷贝。

     第三步:把constructor 重新手动指回来。sbp.constructor=sb

     发现没有?如果采用《指南》里面的写法,必须要有第二步,把不要的属性都删除掉( 不删会怎样?一个是可能会存在属性覆盖的问题,另外就是内存浪费了,当new 出 很多对象来的时候,这种浪费就很可观了哦!) 。如果属性很多,岂不要写很多delete ?而且要一个一个去核对一下超类里面的属性名称,显然Ext 的 作者并不希望这么做。于是有了这几句精彩的var F=function(){} ,定义一个空函 数,里面没有属性。然后F.prototype=sp.prototype 再然后sbp=new F() 这么做的话,就把F.prototype 也 就是sp.prototype 里面的东西拷贝到sb.prototype 里 面了,同时,因为F 是个没有任何属性的函数,所以不需要再delete 任 何东西了。这句真的很精彩哦!

     这时候sb.prototype.constructorF() ,所以再来一句sbp.constructor=sb 。 这样的话就完美地完成了对父类prototype 的拷贝,而又没有把不要的属性拷进来。

     到了这里,关健的两步操作:属性拷贝、方法拷贝(prototype 里 面的) 都已经完成。

     后面的代码就比较简单了,不再解释。

     看完这篇文章你应该能理解这个核心的extend 函数到底完 成了什么操作了,如果还是不明白,我不得不承认,那还是我的错,那么请联系我吧QQ253445528 ,注明“Ext 源码分析”。说明:在5 ×8 小时的上班时间不解答问题。

     这篇文章耗费笔者近三个小时的时间,请尊重原创,转载请注明出处,谢谢。

分享到:
评论

相关推荐

    ExtJS的extend(Ext Designer的使用)

    这篇博客文章可能是关于如何在Ext Designer中利用`extend`来设计和自定义UI组件的教程。 首先,我们要理解`extend`在Ext JS中的工作原理。`extend`关键字用于声明一个类是另一个类的子类。这在JavaScript中实现了类...

    ext 继承(extend) 理解练习

    这里,我们聚焦于`ext`继承,通常指的是JavaScript中的`extend`方法,这是一种实现继承的方式。在JavaScript中,由于语言本身没有内置的类机制,所以开发者通过函数和原型链来模拟类的概念。 `extend`方法通常被...

    Ext一些方法的重写

    在`ext-extend.js`这个文件中,可能包含了对`Ext.extend()`的进一步实现和扩展,或者展示了如何在实际项目中重写`Ext`库的方法。通常,这样的代码示例会包含具体的类定义、方法重写以及如何在应用中使用这些自定义类...

    ext继承重写

    EXT JS中的“extend”关键字是实现继承的关键。当你创建一个新的类并声明它`extend`另一个类时,新类将自动获取父类的所有属性和方法。这使得我们可以基于已有的基础类构建更复杂的组件或功能,无需从头开始编写代码...

    EXT dojochina Ext类静态方法.rar

    例如,`Ext.extend(MyClass, Ext.Panel, {myMethod: function() {...}})`创建了一个新类MyClass,它继承自Ext.Panel,并添加了一个名为myMethod的方法。 - `Ext.apply()`: 这个静态方法用于合并对象的属性,通常...

    com.guo.android_extend:android-extend:1.0.6

    implementation(name: 'android-extend-release', ext: 'aar') } ``` 3. 最后,执行 `gradlew build` 或者在 Android Studio 中同步项目,使库被正确地引入到项目中。 总之,这个压缩包提供了一个名为 "android-...

    Ext.Store的获取方法

    Leangle.form.combo.ColorComboBox = Ext.extend(Leangle.form.BaseComboBox, { // ComboBox configurations store: new Ext.data.JsonStore({ // store configurations }) }); ``` 在这种情况下,尝试使用`Ext...

    ext多选下拉列表的全选功能实现

    "ext多选下拉列表的全选功能实现"这个主题聚焦于一个特定的UI组件——ExtJS库中的MultiComboBox,这是一种允许用户多选的下拉列表控件。在实际应用中,全选功能常常被用来快速选择所有选项,极大地提高了用户的操作...

    Ext继承分析

    在ExtJS中,`Ext.extend()` 是一个核心的函数,用于实现类之间的继承机制。它允许你创建新的类(子类),这些类将继承另一个类(父类)的属性和方法。这个功能对于构建复杂的JavaScript应用程序至关重要,因为它提供...

    Ext继承和扩展

    然而,`Ext`库提供了一个更优雅、更易于理解和维护的方式来处理继承,这包括`Ext.extend()`方法和`Ext.create()`函数。 ### `Ext.extend()` `Ext.extend()`是`Ext`库中用于创建子类的核心方法。它接受三个参数:...

    EXT测试小样例--EXT测试小样例

    EXT测试小样例通常指的是基于EXT JS框架进行的软件测试示例。EXT JS是一个用于构建富客户端Web应用程序的JavaScript库,特别适用于创建数据驱动、交互性强的用户界面。EXT Grid是EXT JS中的一个核心组件,它提供了一...

    ext超酷的grid中放图片(ext3.2.1)

    Ext.extend(Ext.grid.Column, { renderer: function(value, meta, record, rowIndex, colIndex, store) { if (value) { return '图片" />'; } else { return ''; } } }); var grid = new Ext.grid....

    ext api,ext 学习笔记,SWFUpload_多文件上传及部分工具

    "第一章 必须理解Ext_extend函数.mht"和"EXT JS组件事件.mht"可能深入讨论了EXT的扩展机制和组件事件处理,是EXT开发的重要基础。 "EXT 中文手册.pdf"则是EXT的官方中文文档,是学习EXT API的权威参考资料,包含...

    Ext与后台服务器的交互操作

    Mis.Ext.CrudPanel = Ext.extend(Ext.Panel, { // ... 具体实现细节省略 }); ``` 要使用这个CRUD面板,需要进一步继承并实现特定的需求,比如文中给出的例子是创建一个污水厂管理面板: ```javascript ...

    Ext实现java的面向对象实例

    使用`extend`关键字来指定父类,如`Ext.extend('SubClass', 'SuperClass')`。 7. **类实例方法重写**:在子类中,可以重写父类的实例方法,以实现不同的功能。在Ext中,你可以直接在子类中定义相同的方法名来实现...

    ext常用注释

    使用 `Ext.extend` 可以方便地扩展现有的Ext组件类,创建新的组件类型。例如,创建一个搜索面板组件: ```javascript SearchPanel = Ext.extend(Ext.Panel, { constructor: function () { this.addEvents('...

    Ext4 动态加载js例子

    extend: 'Ext.panel.Panel', requires: ['MyApp.util.MyUtil'], // 延迟加载 MyUtil 工具类 // ... }); ``` 当创建 `MyApp.view.MyPanel` 实例时,`MyApp.util.MyUtil` 将自动加载。 4. **路径映射** `paths...

    ext面向对象和继承

    EXTJS中的继承是通过`Ext.extend()`或`Ext.createByAlias()`实现的。继承允许子类重写或扩展父类的方法和属性。例如,我们可以创建一个新的按钮类,继承自EXTJS的`Ext.button.Button`: ```javascript MyButton = ...

    Ext3.0 api帮助文档

    - **Ext JS的类系统基于MVC模式,使用EXT.extend方法实现类的继承**,这使得代码复用和模块化成为可能。 - **配置对象(Configurations)**: 大多数类允许通过配置对象进行初始化,简化了组件的创建过程。 3. **...

Global site tag (gtag.js) - Google Analytics