`

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

阅读更多

强烈声明:转自:http://hi.baidu.com/ihc0100/blog/item/e6fec9b4dc898e7b8bd4b296.html

显然了,从函数名就可以看出来,这个函数是整个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里面的)都已经完成。

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

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

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

分享到:
评论

相关推荐

    jquery.validate.extend.js

    jquery.validate.extend.js

    Ext深入浅出 数据传输

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

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

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

    原生js实现jquery $.extend方法

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

    jQuery.extend 函数详解

    jQuery.extend 函数详解 Jquery的扩展方法extend是我们在写插件的过程中常用的方法,该方法有一些重载原型,在此,我们一起去了解了解。

    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函数

    前面我们扩展了bind方法和ready函数,这次我要讲一下$.fn.extend 和$.extend函数。 其他的不多说,直接切入主题吧! 先来看看这两个函数的区别:  $.fn.extend是为查询的节点对象扩展方法,是基于$的原型扩展的方法...

    Ext_Extend 用法及解读

    ext 的详细解读,以及实际应用,与大家一起分享。

    EXT是一款强大的AJAX框架

    要使用这个CRUD面板,需要继承实现它,我们举一个例子 //继承CrudPanel,创建污水厂管理面板 AddPlantPanel=Ext.extend(Mis.Ext.CrudPanel,{ id:"AddPlantPanel",//id号是表示一个面板的唯一标志 title:"污水厂管理...

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

    NULL 博文链接:https://bijian1013.iteye.com/blog/2281404

    jQuery:jQuery.extend函数详解

    jQuery:jQuery.extend函数详解

    Ext+JS高级程序设计.rar

    第一部分 Ext Core 第1章 Ext Core重要概念 2 1.1 Ext.Element 2 1.1.1 获取HTMLElement节点的Ext.Element实例 2 1.1.2 CSS样式操作 3 1.1.3 DOM查询与遍历 4 1.1.4 DOM操作 6 1.1.5 事件处理 9 1.1.6 尺寸大小 13 ...

    com.guo.android_extend android-extend1.0.6.zip

    implementation 'com.guo.android_extend:android-extend:1.0.6'失败,用这个替代就好了 implementation 'com.guo.android_extend:android-extend:1.0.6'失败,用这个替代就好了

    jquery $.fn.extend

    jquery $.fn.extend 引用事件

    深入理解jquery的$.extend()、$.fn和$.fn.extend()

    下面小编就为大家带来一篇深入理解jquery的$.extend()、$.fn和$.fn.extend()。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    浅谈jquery.fn.extend与jquery.extend区别

    1.jquery.extend(object); 为扩展jQuery类本身.为类添加新的方法。 jquery.fn.extend(object);给jQuery对象添加方法。 $.extend({  add:function(a,b){return a+b;} }); //$.add(3,4); //return 7 jQuery添加...

    Extjs 继承Ext.data.Store不起作用原因分析及解决

    关于这个原因有很多种,我只说下我遇到的 我这样 写Store来复用的 代码如下: DocStore = Ext.extend(Ext.data.Store,{ initComponent:function(){ this.proxy = new Ext.data.HttpProxy({url:this.url}); this....

    com.guo.android_extend:android-extend:1.0.6

    implementation 'com.guo.android_extend:android-extend:1.0.6'失败,用这个替代就好了

    Ext.ux.UploadDialog

    Most configuration options are inherited from Ext.Window (see ExtJs docs). The added ones are: url - the url where to post uploaded files. base_params - additional post params (default to {}). ...

    android-widget-extend

    各种控件组件展示。 支持API10+ 水平滑动listView。 异步加载图片。 双指缩放,拖动。 项目链接:https://github.com/gqjjqg/android-widget-extend

Global site tag (gtag.js) - Google Analytics