`

《仔仔细细分析Ext》 第一章 必须理解Ext.extend函数

阅读更多

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

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

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

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

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

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

  

1、prototype

RectAngle=function (width,height){
     this.width=width;
     this.height=height;
}
RectAngle.prototype.area=function(){
     return this.width*this.height;
}

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

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

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

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

2、constructor

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

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

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

   言归正传,对于每个RectAngle的实例来说,例如var rect=new RectAngle(10,10); rect.prototype会指向构造函数RectAngle的prototype,也就是说所有的实例都会共享同一份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构造函数内部,外部函数无法直接访问RectAngle的width和height属性,只能通过执行getWidth()和getHeight()方法来获得这两个属性的值。

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

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

   ⑴定义私有属性:从上面的代码可以看出来,外部函数是没有办法直接引用width和height这两个属性的,比如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);按理说,在构造函数执行完毕之后,width和height这两个变量就应该释放内存了,但是通过类似这种this.getWidth=function(){return width}的定义,width和height变量并不释放内存,否则在外部调用getWidht()的时候,就无法返回对应对象的width值了。

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


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


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

  


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){
    RectAngle.call(this,w,h);
    this.c=color;
}

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

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

   var rect=new RectAngle();

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

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

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

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

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

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

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

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

Delete ColoredRectAngle.prototype.w;

delete ColoredRectAngle.prototype.h;

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

   看上去世界一片和谐。但是...还有一个暗藏的问题,请看:第三:这个时候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的实例来说,就不能确切地知道它的constructor是ColoredRectAngle了。

   所以,需要手动地把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属性,就用overrides的constructor当作子类的构造函数。否则,创建个新的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.constructor是F(),所以再来一句sbp.constructor=sb。这样的话就完美地完成了对父类prototype的拷贝,而又没有把不要的属性拷进来。

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

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

分享到:
评论

相关推荐

    jquery.validate.extend.js

    jquery.validate.extend.js

    Jquery实现$.fn.extend和$.extend函数_.docx

    在JavaScript中,jQuery库提供了两种扩展...以上代码模拟了jQuery中`$.fn.extend`和`$.extend`的基本实现,可以帮助理解它们的工作原理。在实际开发中,这两个方法被广泛用于自定义jQuery插件和扩展jQuery的核心功能。

    Ext深入浅出 数据传输

    第12 章 一个完整的EXT 应用......................... 317 12.1 确定整体布局........................................ 317 12.2 使用HTML和CSS设置静态信息.......... 319 12.3 对学生信息进行数据建模.............

    jQuery.extend 函数详解

    ### jQuery.extend 函数详解 #### 一、概述 在JavaScript前端开发中,jQuery是一个非常流行的库,它简化了许多常见的操作,比如DOM操作、事件处理、AJAX交互等。`jQuery.extend`是jQuery提供的一个用于扩展jQuery...

    underscore.extend与$.extend实例比较分析

    在JavaScript开发中,`underscore` 和 `jQuery` 都提供了...在阅读《underscore.extend与$.extend实例比较分析》这篇博客后,开发者可以根据实际项目中的具体场景来选择使用哪一个工具,以便更高效地处理对象扩展问题。

    原生js实现jquery $.extend方法

    原生js实现jquery $.extend方法 通过遍历对象属性来实现

    jquery $.fn.extend

    `$.fn.extend`是jQuery库中的一个核心方法,主要用于扩展jQuery对象的方法集合。这个方法允许开发者自定义jQuery的函数,从而实现对DOM元素的操作或添加新的功能。在jQuery中,`$.fn`实际上是`$.prototype`的一个...

    Ext.js核心函数详解.pdf

    在"Ext.js核心函数详解.pdf"中,我们看到了一些关键的Ext.js函数,这些函数对于理解和使用这个框架至关重要。下面是对这些函数的详细说明: 1. `Ext.apply(Object obj, Object config, Object defaults)`: 这个函数...

    Ext.js核心函数详解.docx

    4. `Ext.extend( Object subclass, Object superclass, [Object overrides] )`: 这是实现面向对象编程的关键函数,它用于创建子类,将`subclass`扩展自`superclass`。`overrides`参数用于提供子类覆盖父类的方法或...

    Ext Js权威指南(.zip.001

    第1章 ext js 4开发入门 / 1 1.1 学习ext js必需的基础知识 / 1 1.2 json概述 / 3 1.2.1 认识json / 3 1.2.2 json的结构 / 3 1.2.3 json的例子 / 4 1.2.4 在javascript中使用json / 4 1.2.5 在.net中使用...

    Jquery实现$.fn.extend和$.extend函数

    在jQuery库中,`$.fn.extend` 和 `$.extend` 是两个非常重要的功能,它们用于扩展jQuery的功能和对象。这两个函数虽然名字相似,但作用却截然...理解这两个函数的工作原理对于深入学习jQuery和开发jQuery插件至关重要。

    jQuery:jQuery.extend函数详解

    `jQuery.extend`是jQuery库中一个非常实用且功能强大的函数,主要用于合并两个或多个对象的属性至一个新的对象中。这使得开发人员能够在编写插件或其他代码时方便地扩展jQuery的功能,或者简单地合并配置选项。 ###...

    EXT dojochina Ext类静态方法.rar

    EXT dojochina Ext类静态方法是一个关于EXT框架在JavaScript中的使用的主题,主要聚焦于Ext类的静态方法。EXT是一个强大的前端开发框架,由Sencha公司开发,它提供了丰富的组件库,用于构建复杂的Web应用程序。在EXT...

    jQuery.extend和jQuery.fn.extend的区别

    其次,`jQuery.fn.extend`是jQuery的一个重要特性,它用于扩展jQuery的函数集,也就是jQuery对象的方法。`jQuery.fn`实际上是`jQuery.prototype`的别名,因此`jQuery.fn.extend`实际上是在原型对象上添加新方法。...

    ext 继承(extend) 理解练习

    在给出的`test_extend.html`文件中,可能包含了一个示例,演示了如何使用`extend`方法来创建和继承自定义的JavaScript对象。打开这个HTML文件,你应该能看到一个实际的代码示例,帮助你更好地理解`extend`的用法。 ...

    com.guo.android_extend:android-extend:1.0.6

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

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

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

Global site tag (gtag.js) - Google Analytics