网站导航免费论文 原创论文 论文搜索 原创论文 网学软件 学术大家 资料中心 会员中心 问题解答 原创论文 论文素材 设计下载 最新论文 下载排行 论文上传 在线投稿 联系我们
返回网学首页
最新论文 推荐专题 热门论文 素材专题
当前位置: 网学 > 网页素材 > AJAX代码 > 正文
ExtJS 2.2Function的扩展
来源:Http://myeducs.cn 联系QQ:点击这里给我发消息 作者: 用户投稿 来源: 网络 发布时间: 11/01/18
返回深入学习ExtJS 2.2开发系列连载教程目录
函数是Javascript中最重要的概念,它如面对对象中的方法一样,但是它却能用来模拟类的实现。更为强大的功能是采用new Function()的方式能动态生成函数。还能改变函数的作用域,这里就不多说它的基本功能。

3.2.1从一个错误说起
笔者的一位朋友在开发中碰到这样一个问题,采用for循环动态为多个dom元素添加元素处理函数。结果总是出错莫名其妙的错误,它一直认为是浏览器的问题。下面就采用一个简单的例子要模拟这种情况。
<body>
    <div id="test1">test1</div>
    <div id="test2">test2</div>
    <div id="test3">test3</div>
    <script type="text/javascript">        
       function addEvent(elem,type,fn){
           if (elem.addEventListener)// FF
              elem.addEventListener(type, fn, false);
           else if (elem.attachEvent)// IE
              elem.attachEvent("on" + type, fn);
           }      
        var doms=document.getElementsByTagName("div");
       var length=doms.length;
       for (var i=0;i<length;i++){                                    
       var m="you click test"+(i+1);                      ①
       var callback=function(){alert(m);};               ②  
       addEvent(doms[i],''click'',callback);
       }      
       </script>
    </body>
这个例子中,设计的本意是当点击test1时,会alert出you click test1,点击test2时,弹出you click test2.但是结果都不是。无论点那一个都是弹出you click test3。

感觉上面没有错误啊,②处的闭包函数引用①处的变量值。每一次都把值传到并存放在②中。在事件触发时就调用②处的处理函数。其实这样理解闭包对变量的引用是错误的。在第一次运行的时候,m的确是you click test1。但是click事件肯定是在这段代码运行之后才触发的。那个时候再根据②找到①处的变量。因为在之后的二次循环中,m变量的值已经改变了,变成you click test3。在闭包引用的变量中是不是回收内存的,但是其变量是可以通过一定的方式改变的,这里就是一个例子。

明白了出错的原因,接下就是如何去解决这个问题。这个问题在事件的处理中非常典型的。把多个参数的函数变成无参数或只有一个event参数的函数。上面的例子是想通过闭包的形式来解决。在单一的事件中是可以做到。对于这样形式的多元素的事件注册,其闭包引用的变量要发生变化。这样的做法是行不通的。

那有什么解决办法呢?其功能很简单,就是形成一个函数。这个函数能保存各自的引用的闭包变量值。要解决的方法也还是采用闭包的形式来解决的。先把上面的代码②用var callback=delegate(m);代替。再在代码清单3.9的addEvent函数之前加上一个下面的函数:var delegate= function(m){
       return function(){alert(m);}    
   };
这段代码与②处的函数的区别在于通过函数参数形式传入m。同时函数内部采用了闭包函数。好像区别不大。那为什么这里能保存值,而上面②处就不能呢?代码清单3.10中的m不再是像②处一样,它是存在delegate的外部函数的实参中。每次循环,每次都传入不同的实参,都会建立一个堆栈用来保存,如果是闭包引用的变量,这个就不释放。而返回的内部函数就是取这个没有释放的实参m,故能保存数据。

对于这个例子,可以这样解决。但是有没有一个通用的解决方法。就是把这个给通用起来。Ext中对函数的扩展createDelegate就是完成这方面功作的。

3.2.2 函数的委托
对比代码清单3.10和代码清单3.9中②处的代码,可以发现清单3.10中就是②处的函数外面包裹了一个函数。其它什么事都没有什么用。清单3.9中②处是直接把函数传出callback回调。而在改进的清单3.10的函数调用是先执行这个包裹的函数(var callback=delegate(m))。

我们可以把②处的函数看作是功能函数,而在清单3.10中的函数看作代理函数。代理函数是功能函数进了代理工作。那们能不能从功能函数出发,让它委托代理函数来在自身的基础完成一定的特殊功能呢?代理只是完成一些辅助性有功能,而实质具体的功能都在功能函数中完成。这样说来代理函数就可以针对某一类形抽象起通用的解决方法。如上面的保存数据的代理功能一样。

我们从函数的代理的源码的角度来思考,可以把函数的代理分成三类,改变功能函数作用域,改变功能函数的参数形式,改变功能函数的内容。

1、改变功能函数作用域。改变函数的作用域的方法,Javascript的Function的方法就有apply和call这两个方法。但是这两个方法在改变作用域的同时会执行这个函数。我们现在要的还是函数,还要改变作用域。这个只要在apply或call的应用之外套上一个函数的形式。那不是成了函数吗?在执行它的时候就会执行apply或call来改变其作用域。这个作用域当然得通过参数来传入的,达到改变。

2、改变功能函数的参数形式。在功能函数中可能会有多个参数,但是想在调用这个功能函数之前,先给它传入一些参数。传入参数就是调用函数。现在要的还是函数,只不过参数比功能函数的参数会少一些。这个可能通过把预先传入的参数和调用时传入的参数组合起来,一并传到这个功能函数中去。也就是说传参的过程分成至少两个部分了。

3、改变功能函数的内容。对于功能函数上,有的时候并不能满足完全的功能。全部重写的话就显得冗余。那么我们可以在功能函数上加上一些实用功能。比如在运行这个功能函数之前或之后加上一些处理。或者还可以把这个功能函数包裹在另外一个传入参数Fn的内部。

在prototype1.6中,bind,bindAsEventListener就是完成改变作用域的方法,而curry、methodize则是实现了改变功能函数的参数形式。改变功能函数的内容就是wrap来完成了。

接下来就说说Ext中的实现了。Ext对函数的Prototype扩展了五个方法。其中createCallback 没有什么大的作用。createDelegate是集大成函数,它实现改变函数参数和作用域两种功能。createSequence和createInterceptor从Aop拦截的思想要实现改成功能函数内容。

在Ext中,其对函数的扩展createDelegate是一个值得仔细去分析的函数的。它能改变功能函数的作用域,还能改变成参数。3.2.1中的保存数据也可以通过其createDelegate来完成。对于保存数据,我们还是通过改变功能函数的参数来完成的。对于这种引用的方法,我们要想到其参数是保存数据的。我们看下应用createDelegate的例子。它首先得引用Ext.js文件。

使用createDelegate实现:for (var i=0;i<length;i++){
       var m="you click test"+(i+1);
       var callback=function(m){alert(m);}; ①
       var delegate=callback.createDelegate(this,[m]); ②
       addEvent(doms[i],''click'', delegate);  
}
上面的代码中先是创建一个callback的功能函数。但是调用Ext扩展的createDelegate函数创建了一个对callback的代理函数。这个代理函数改变功能函数的参数。同时也保存了变化的m变量中数据。

如果我还想显示当前dom元素的点击位置信息呢?如我们将①处改成

var callback=function(m,e){alert(''在坐标X:''+e.clientX+'',Y:''+e.clientY+''的位置上,''+m);};把②改成var dele=function(){callback.createDelegate(this,[m],0)();}; ②在原来的代码之上多了一个参数0,这个是指定代理的m参数插入到回调函数callback参数中的第几个位置,从0开始。

我们分析一下createDelegate的源码,看看它是如何实现的。
createDelegate源码createDelegate : function(obj, args, appendArgs){//args可以为单个元素        var method = this;//当前的功能函数

        return function() {

            var callArgs = args || arguments;

            if(appendArgs === true){//把预传入的参数追加在调用时参数的后面。①

                callArgs = Array.prototype.slice.call(arguments, 0);

                callArgs = callArgs.concat(args);//单个元素或数组

            }
else if(typeof appendArgs == "number"){                    ②

                callArgs = Array.prototype.slice.call(arguments, 0);                             //splice第一个参数表明index,插入或删除的开始位置,

//第二参数how many表明从index位置开始删除多少个元素。

//之后的参数是从index的位置开始插入的元素

                var applyArgs = [appendArgs, 0].concat(args);           ③          Array.prototype.splice.apply(callArgs, applyArgs);  

            }
//执行功能函数。采用改变的作用域和参数

            return method.apply(obj || window, callArgs);

        }
;

    },
上面的createDelegate通过obj参数来指定代理函数的作用域。如果要进行参数改变时,一定要提供obj的参数(那怕是null)。在①实现把预传入的参数追加在调用时参数的后面。在 ②处则实现把预传入的参数插入在功能函数参数中指定的位置上。这个位置index由appendArgs参数指定。

③处的applyArgs是用来作为splice函数的参数,如果appendArgs不为数字,如没有指定或指定为true等,那么splice函数就不会去改变callArgs中数据。对于没有指定appendArgs,如果指定了args参数,那么就采用args数组中的数据取代调用时指定的参数做为代理函数的实参。没有指定那当然就是取调用时指定的参数。

一般来说都会采用实际调用中指定的参数,在这里传入的参数会附加到代理函数的参数中,附加有两种方式,一种是追加,加在指定实参后面,一种是插入到指定参数的位置,对于插入,一般都是插入指定实数的前面。也就是appendArgs为0的位置。如果插入到其它的位置会导致调用时混乱。

上面的createDelegate有点小小的不足,就是其不能让其默认的作用域指向当前的this对象。如果上面的例子,在传通的事件回调函数中,都是注册改事件的元素是默认是this对象,在回调函数中可以通过this.id来获得当前元素的id。但是在通过createDelegate就得在创建时来分别指定其作用域。有的情况,这个this是不能通过scope参数来指定的。我们可以把createDelegate只要把最后一句改成return method.apply(obj ||this|| window, callArgs);这样就可以通过传入第一个参数为null的表明是指定当前对象。如果想不传入第一个参数时还想改变功能函数的参数(即有第二,三个参数)。那么就得在只允许args为数组的情况下才能进行判断的。本来这个参数它也是规定为数组。其改动为在函数内部的开始位置加上:if(Ext.isArray(obj)) obj=null;这样的话,只要通过xx. createDelegate([aa,bb,cc],0)的方式就实现了prototype中curry的功能。

Ext中还提供了periodical 和defer两个处理时间间隔或推迟执行的方法。这两个方法是建立在createDelegate之上先构建一个代理方法,之后再通过setInterval或setTimeout来执行这个代理方法。其代码简单,不作分析。

关于其下的两个拦截的方法,createSequence和createInterceptor,一个是在功能函数之前或之后执行参数传入的函数。也不作分析。

从上面的可以看出,对于函数的扩展,也部分间接或者部分实现了代码的重用。对于代理的重用来讲,prototype1.6中methodize就是专门为这个而设计的。我们很多功能函数,如Ext.each(array,fn),它的第一个参数是数组。如果要把它重用到array.each(fn)中去怎么办呢?因为Ext.each是函数,那么只在把this使为参数传给第一个array参数就可以了。我们看一下methodize:methodize: function() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
        return __method.apply(null, [this].concat($A(arguments)));
    }
;
  }
});
我可以通过 Array.prototype.each=Ext.each.methodize();这样每一个数组都可以通过[xx,yy].each(fn)来调用Ext.each来完成任务。Prototype1.6的架构上的代码重用很多部分都是采用了这种方式。 var createSingle = function(h, e, fn, scope){
        return function(){
            e.removeListener(fn, scope);
            return h.apply(scope, arguments);
        }
;
    };
  • 上一篇资讯: ExtJS 2.2type和eval
  • 下一篇资讯: ExtJS 2.2数据及集合
  • 网学推荐

    免费论文

    原创论文

    浏览:
    设为首页 | 加入收藏 | 论文首页 | 论文专题 | 设计下载 | 网学软件 | 论文模板 | 论文资源 | 程序设计 | 关于网学 | 站内搜索 | 网学留言 | 友情链接 | 资料中心
    版权所有 电话:013574892963 QQ:3710167 邮箱:Educs@163.com 网学网 [Myeducs.cn] 您电脑的分辨率是 像素
    Copyright 2008-2015 Www.myeducs.Cn All Rights Reserved
    湘ICP备09003080号