`

ExtJs源码分析与学习—ExtJs事件机制(四)

阅读更多

ExtJs组件事件——ExtJs自定义事件的处理

      下面通过对组件的事件对象和事件机制两个方面分别对源码进行分析。

组件事件对象

      ExtJs组件事件对象是通过Ext.util.Event类来完成的,其实在浏览器元素事件中部分功能的实现已用到了该类。下面看该类的实现代码

 

EXTUTIL.Event = function(obj, name){
    this.name = name;//事件名
    this.obj = obj;//作用域
    this.listeners = [];//多个监听函数的集合
};

 这里说明一点,ExtJs为了实现更高的压缩,把Ext.util定义为常量EXTUTIL来处理。
该类是个集合类,它保存了多个监听函数,同时还需要和某个事件名对应起来。该类还实现了存放、删除、查找监听函数等相关操作。下面看具体的实现方法。

 

addListener : function(fn, scope, options){
        var me = this,
            l;
        scope = scope || me.obj;
        if(!me.isListening(fn, scope)){//判断fn参数是否已经有同作用域的函数存在
            l = me.createListener(fn, scope, options);//集合并不是保存监听函数,而是存放该函数的相关信息
            // if we are currently firing this event, don't disturb the listener loop
            // 如果当前事件正在执行,为了不改变该事件的监听函数集合,重新复制当前集合,然后赋给listeners
            // 并利用me.listeners.push(l)加入新的监听函数,而原来的集合中正在被执行的代码在使用完成之后会垃圾回收,下一次执行的是新的集合,注意这种方式的实现
            if(me.firing){ 
                me.listeners = me.listeners.slice(0);//实现了复制功能
            }
            me.listeners.push(l);
        }
},

 

存放监听函数,首先判断fn参数是否已经有相同作用域的函数存在,如果存在则不会重复增加。l = me.createListener(fn, scope, options);代码返回的值并不是集合要保存的监听函数,而是存放该函数的相关信息,格式为:
this.listeners = [{fn : fn, scope : scope, options : o, fireFn: h}]
下面看createListener是怎么包裹监听函数的。

 

createListener: function(fn, scope, o){
        o = o || {};
        scope = scope || this.obj;
        var l = {
            fn: fn,
            scope: scope,
            options: o
        }, h = fn;
        if(o.target){
            h = createTargeted(h, o, scope);
        }
        if(o.delay){
            h = createDelayed(h, o, l, scope);//延迟运行
        }
        if(o.single){
            h = createSingle(h, this, fn, scope);//该监听函数只运行一次
        }
        if(o.buffer){
            h = createBuffered(h, o, l, scope);//缓存运行
        }
        l.fireFn = h;
        return l;
},

 

该函数中关于delay和buffer的区别,将在后续(定时器的讲解)给出答案。
创建完监听函数对象后,我们来看看他是如何执行的以及多个注册的监听函数执行顺序。ExtJs是通过Ext.util.fire来执行的,代码如下

 

    fire : function(){
        var me = this,
            listeners = me.listeners,
            len = listeners.length,
            i = 0,
            l;

        if(len > 0){
            me.firing = TRUE;//表明正在执行
            var args = Array.prototype.slice.call(arguments, 0);
            for (; i < len; i++) {
                l = listeners[i];
                if(l && l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
                    return (me.firing = FALSE);
                }
            }
        }
        me.firing = FALSE;
        return TRUE;
    }

 该函数通过传入的参数,来确定要执行的事件名(监听函数)

 

 

另一个比较重要的方法就是删除监听函数,代码如下

 

    /**
     * 删除监听函数(事件)
     */
    removeListener : function(fn, scope){
        var index,
            l,
            k,
            me = this,
            ret = FALSE;
        if((index = me.findListener(fn, scope)) != -1){
            if (me.firing) {
                me.listeners = me.listeners.slice(0);
            }
            l = me.listeners[index];
            if(l.task) {
                l.task.cancel();
                delete l.task;
            }
            k = l.tasks && l.tasks.length;
            if(k) {
                while(k--) {
                    l.tasks[k].cancel();
                }
                delete l.tasks;
            }
            me.listeners.splice(index, 1);//删除
            ret = TRUE;
        }
        return ret;
},

 

而方法clearListeners为删除所有监听函数

 

clearListeners : function(){
        var me = this,
            l = me.listeners,
            i = l.length;
        while(i--) {
            me.removeListener(l[i].fn, l[i].scope);
        }
    },

 

 

组件事件机制

      组件事件机制是采用观察者模式实现的,即一部分由类库来实现(观察者),另一部分由开发者来实现(被观察者)。ExtJs组件事件机制是在Ext.util.Observable类中实现的,只要继承了该类就拥有了事件处理的能力。首先来看代码中的观察者,观察者用来观测开发者是否实现了该接口,如被观察者实现了该接口就会执行该接口对应的功能,而定义该接口是由观察者来编写的。在Ext.util.Observable中,观察者是由函数addEvent来实现的。

 

addEvents : function(o){
        var me = this;
        me.events = me.events || {};
        if (typeof o == 'string') {
            var a = arguments,
                i = a.length;
            while(i--) {
                me.events[a[i]] = me.events[a[i]] || TRUE;
            }
        } else {
            Ext.applyIf(me.events, o);//事件名及其对应的事件对象都存放在events中 o应该为{'eventName':true}格式
        }
},

该函数有两种调用形式
this.addEvents({"fired" : true, "quit" : onQuit});// onQuit为函数
this.addEvents("fired","quit");
该函数只在events中保存了事件名,这样做的好处是减少内存的占有,提高效率,在addListener才会正在的添加监听函数。运行监听函数是通过fireEvent函数来实现的,代码如下

 

fireEvent : function(){
        var a = Array.prototype.slice.call(arguments, 0),
            ename = a[0].toLowerCase(),//事件名
            me = this,
            ret = TRUE,
            ce = me.events[ename],
            cc,
            q,
            c;
        if (me.eventsSuspended === TRUE) {//支持组件的事件挂起
            if (q = me.eventQueue) {
                q.push(a);
            }
        }
        else if(typeof ce == 'object') {
            if (ce.bubble){//是否能进行冒泡处理
                if(ce.fire.apply(ce, a.slice(1)) === FALSE) {//触发该事件,调用Ext.util.Event.fire,返回false结束下面的操作
                    return FALSE;
                }
                c = me.getBubbleTarget && me.getBubbleTarget();//进行冒泡的元素
                if(c && c.enableBubble) {
                    cc = c.events[ename];
                    if(!cc || typeof cc != 'object' || !cc.bubble) {
                        c.enableBubble(ename);
                    }
                    return c.fireEvent.apply(c, a);
                }
            }
            else {
                a.shift();
                ret = ce.fire.apply(ce, a);
            }
        }
        return ret;
    },

该函数第一个参数是用来指定事件名,之后的n个参数将用作监听函数的参数,在调用该方法时应该至少要有事件的名称,方法中判断了是否进行冒泡处理。该方法的调用如下
this.fireEvent(‘disable’,this);//Ext.Component类中的disable事件。
在实际开发中,要想使组件或类拥有事件能力,首先需要继承Ext.util.Observable,然后调用上面的方法给它注册事件名和建立监测点。接下来看被观察者

 

被观察者就是开发者编写的事件监听函数,并把他注册到相对应的事件中,让组件监测它。这个注册动作是由方法addListener函数来完成的,代码如下:

 

addListener : function(eventName, fn, scope, o){
        var me = this,
            e,
            oe,
            ce;
        //采用紧缩形式的参数对象形式
        /*
         * 例如
           myGridPanel.on({
			'click' : this.onClick,
			'mouseover' : this.onMouseOver,
			'mouseout' : this.onMouseOut,
			 scope: this
			});
         */
        if (typeof eventName == 'object') {
            o = eventName;
            for (e in o) {
                oe = o[e];
                if (!me.filterOptRe.test(e)) {
                	//私有配置项和共享配置项注册事件
                    me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
                }
            }
        } else {
            eventName = eventName.toLowerCase();
            ce = me.events[eventName] || TRUE;
            //如果没有调用addEvents,即addEvents中没有生成事件对象,推迟到这里实现,所以即使在观察者时没有调用addEvents对该事件进行注册也可以
            if (typeof ce == 'boolean') {
                me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
            }
            ce.addListener(fn, scope, typeof o == 'object' ? o : {});
        }
    },

 

此处实现与元素事件(浏览器事件的注册)类似,不同之处这里的配置项只有四种:scope|delay|buffer|single

 

 

下面看删除事件方法

removeListener : function(eventName, fn, scope){
        var ce = this.events[eventName.toLowerCase()];
        if (typeof ce == 'object') {
            ce.removeListener(fn, scope);
        }
    },

 该方法中调用Ext.util.Event.removeListener来删除已注册的事件

 

 删除所有的监听事件,代码如下

   purgeListeners : function(){
        var events = this.events,
            evt,
            key;
        for(key in events){
            evt = events[key];
            if(typeof evt == 'object'){
                evt.clearListeners();
            }
        }
    },

 

 

ExtJs还提供了基于拦截的事件实现,它采用拦截思想(AOP)。具体来说可以采用Ext.util.Observable类的beforeMethod和afterMethod方法来实现拦截的功能。代码如下

   // adds an 'interceptor' called before the original method
        beforeMethod : function(method, fn, scope){
            getMethodEvent.call(this, method).before.push({
                fn: fn,
                scope: scope
            });
        },

        // adds a 'sequence' called after the original method
        afterMethod : function(method, fn, scope){
            getMethodEvent.call(this, method).after.push({
                fn: fn,
                scope: scope
            });
        },

 

 这两个方法都统一调用了getMethodEvent方法,代码如下

 

function getMethodEvent(method){
        var e = (this.methodEvents = this.methodEvents || {})[method], //用来保存事件对象
            returnValue, v, cancel, obj = this;

        if (!e) {//该参数中传入的函数尚未注册
            this.methodEvents[method] = e = {};
            e.originalFn = this[method];
            e.methodName = method;
            e.before = [];
            e.after = [];

            var makeCall = function(fn, scope, args){//用来执行before和after集合的事件监听函数
                if((v = fn.apply(scope || obj, args)) !== undefined){//运行监听函数
                    if (typeof v == 'object') {//如果返回值是对象时,取得对象中的returnValue或返回值
                        if(v.returnValue !== undefined){
                            returnValue = v.returnValue;
                        }else{
                            returnValue = v;
                        }
                        cancel = !!v.cancel;//如果该返回值指明cancel为true,那么就不执行接下来的函数
                    }
                    else
                        if (v === false) {
                            cancel = true;
                        }
                        else {
                            returnValue = v;
                        }
                }
            };

            this[method] = function(){//把传入的函数名指定的函数封装成执行的函数序列函数
                var args = Array.prototype.slice.call(arguments, 0),
                    b;
                returnValue = v = undefined;
                cancel = false;

                //执行before事件处理函数
                for(var i = 0, len = e.before.length; i < len; i++){
                    b = e.before[i];
                    makeCall(b.fn, b.scope, args);
                    if (cancel) {
                        return returnValue;
                    }
                }

                //执行传入的函数名指定的函数
                if((v = e.originalFn.apply(obj, args)) !== undefined){
                    returnValue = v;
                }

                //执行after事件处理函数
                for(var i = 0, len = e.after.length; i < len; i++){
                    b = e.after[i];
                    makeCall(b.fn, b.scope, args);
                    if (cancel) {
                        return returnValue;
                    }
                }
                return returnValue;
            };
        }
        return e;
}

 

当调用beforeMethod或afterMethod方法,它就通过了getMethodEvent方法生成一个与它指定的拦截的函数名对应的方法事件对象。这个对象中的有before和after两个集合属性。调用beforeMethod方法就会向这个before集合加入事件的监听方法。调用afterMethod方法则向after集合中添加。

 

当调用该方法后,会在methodEvents集合中保存事件对象,这个对象中事件名就是拦截的方法名,它的格式如下:
{originalFn:onselect,methodName:’onselect’,before:[],after:[]}。
其中originalFn是指拦截的方法,methodName就是拦截的方法名。接着的before和after就是用来保存该拦截方法的之前或之后执行的函数。
其中makeCall是用来执行before和after集合的监听函数,程序中


            //执行before事件处理函数
                for(var i = 0, len = e.before.length; i < len; i++){
                    b = e.before[i];
                    makeCall(b.fn, b.scope, args);
                    if (cancel) {
                        return returnValue;
                    }
                }

              //执行after事件处理函数
                for(var i = 0, len = e.after.length; i < len; i++){
                    b = e.after[i];
                    makeCall(b.fn, b.scope, args);
                    if (cancel) {
                        return returnValue;
                    }
                }

通过循环来分别运行它们各自己集合的监听函数。而此处
              //执行传入的函数名指定的函数
                if((v = e.originalFn.apply(obj, args)) !== undefined){
                    returnValue = v;
                }

是运行被拦截的方法。看出来,makeCall是包裹函数,它的包裹的主要作用就是处理返回值。
      对于返回值,getMethodEvent建立了两个变量returnValue、cancel用来保存每次运行时返回的状态,每次运行makeCall都会设定(改变或保持)其值。returnValue是保存其返回值,而cancel的意思是在运行完这个函数之后是否立马中断,不执行该函数链中其它函数。如果cancel为true时中断执行,实现拦截的效果。

 

看下面的例子

 

 

<input type="button" value="点击我" id="btn">

Ext.onReady(function() {
	Ext.BLANK_IMAGE_URL = '../ext/resources/images/default/s.gif';
	Ext.QuickTips.init();
	// 事件拦击器的实现
	var Interceptor = Ext.extend(Ext.util.Observable, {
				constructor : function(config) {
					Ext.apply(this, config);
					this.addEvents('select');//添加观察者事件
					this.beforeMethod('onSelect', this.beforeSelectFn, this);//拦截器
					this.afterMethod('onSelect', this.afterSelectFn, this);
					Interceptor.superclass.constructor.call(this);
				},

				fireInterceptorEvent : function() {
					this.fireEvent('select', this.onSelect, 'arg1',
							'arg2');
				},

				beforeSelectFn : function() {
					alert('before');
					return {
						returnValue : 'interceptor',
						cancel : true
					}
				},

				afterSelectFn : function() {
					alert('after');
				}
			});

	var example = new Interceptor({
				onSelect : function() {
					var a = Array.prototype.slice.call(arguments, 1);
					Ext.each(a, function(item, index, obj) {
								alert(item);
							});
				}
			});
	//被观察者
	example.addListener('select', example.onSelect, this);
	var buttonEl = Ext.get('btn');
	buttonEl.on('click', function(e, t, o) {
				example.fireInterceptorEvent();
			}, this);

});

 

      运行该代码时,首先添加观察者事件和拦截对象,内存中的快照(部分)为:
Ext.util.Observable中
this.methodEvents = {onSelect:{originalFn : onSelect,methodName:'onSelect',after :[{scope:scope,fn:afterSelectFn}],before :[{scope:scope,fn:beforeSelectFn}]}}

 

在实例化Interceptor对象后,添加被观察者时Ext.util.Observable.addListener中会初始化

this.events[eventName] = ce = new EXTUTIL.Event(this, eventName);,此时会把Ext.util.Observable对象传入Ext.util.Event,当然包括属性methodEvents。当点击按钮时,会调用Ext.util.Observable.fireEvent,而该方法中又会调用EXTUTIL.Event.fire执行firefn函数,即beforeSelectFn,见代码
if(l && l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
                    return (me.firing = FALSE);
                }

 

      以上详细的介绍了类Ext.util.Observable。Ext强大的事件功能远不至此,它还提供了与事件相关的快捷键、导航键和鼠标按键事件等。且听下回分解。

 

分享到:
评论

相关推荐

    EXTJS源码分析与开发实例宝典-开发的效果图.rar

    在《EXTJS源码分析与开发实例宝典》这本书中,读者可以深入理解EXTJS的内部机制,学习如何利用EXTJS进行高效开发。 标题"EXTJS源码分析与开发实例宝典-开发的效果图.rar"暗示了书中的内容可能包含了EXTJS的源码解析...

    Extjs源码分析与开发实例宝典

    《Extjs源码分析与开发实例宝典》全面介绍了ExtJS的技术细节和开发实践,是学习和掌握ExtJS不可或缺的资源。通过对本书的深入阅读,开发者不仅可以理解ExtJS的工作原理,还能学会如何运用ExtJS构建高效、美观的Web...

    资料:包括extjs2.0源码

    6. **图表组件**:EXTJS 2.0内置了各种图表类型,如柱状图、饼图、线图等,适用于数据可视化需求,源码分析有助于定制高级图表功能。 7. **Ajax交互**:EXTJS 2.0通过Ajax技术实现与服务器的异步通信,提供了强大的...

    Django整合Extjs源码

    **Django整合ExtJS源码解析** 在Web开发领域,Django作为一个强大的Python Web框架,以其高效、安全和可扩展性而备受青睐。与此同时,ExtJS是一个JavaScript库,用于构建富客户端应用程序,提供了丰富的组件和数据...

    ExtJS 3.4 源码包

    - **调试工具**:利用浏览器的开发者工具,可以追踪ExtJS源码中的错误和性能瓶颈。 - **主题定制**:ExtJS允许自定义皮肤,通过修改CSS文件,可以调整应用的视觉样式。 6. **与其他技术集成** - **PHP, ASP.NET,...

    ExtJs 实例+ExtJs中文教程(学习extjs必备)

    最后,结合源码分析,提升你的编程水平,能够更好地应对复杂的应用需求。 总之,ExtJs是一个强大的JavaScript框架,通过学习提供的实例和中文教程,结合具体的代码实践,你将能够开发出功能丰富、用户体验优秀的Web...

    ExtJS源码分析与开发实例宝典--书中代码

    核心技术部分深入讲解Ext JS的核心基础知识,包括JS原生对象的扩展、事件机制、 模板模型、数据模型,包括一个机制、两个模型及六个扩展。基于元素的开发部分讲解了在DOM元素基 础上扩展的Ext JS元素,包括元素操作...

    Extjs 4.0 源码说明文档入门手册 和示例

    源码分析: 在`ext-4.0.0`目录中,你将找到ExtJS 4.0的核心源代码。这些文件主要分为以下几个部分: 1. `src`目录:这是ExtJS的核心源码存放处,包含了所有组件、类和工具函数。每个主要的组件或功能都有自己的子...

    掏钱学ExtJs完全版附全部源码- 康海涛

    康海涛的"掏钱学ExtJs完全版"可能是一份全面的学习资料,涵盖了ExtJs的基础到高级应用,包括源码分析,旨在帮助学习者深入理解和掌握这一技术。 首先,ExtJs的核心是其组件模型。它提供了大量的预定义组件,如表格...

    extJS3.1源码及demo

    通过下载并研究"ext-3.1.0"这个压缩包中的源码和示例,开发者不仅可以学习到ExtJS的基本用法,还能深入理解其内部机制,这对于提升JavaScript开发技能和扩展自定义功能非常有帮助。不过需要注意,随着技术的发展,...

    EXTJS部分中文源码

    EXTJS的源码学习可以帮助开发者深入理解其内部机制,如数据管理、事件系统、布局管理等。通过分析源码,开发者可以学习到如何自定义组件,优化性能,甚至开发自己的EXTJS插件。此外,熟悉EXTJS的源码也有助于解决在...

    ExtJs源码以及文档相关资料

    这个资料包“ExtJs源码以及文档相关资料”显然包含了ExtJS 4.1.1版本的源代码和相关的文档,这对于深入理解ExtJS的工作原理和学习如何使用它是极为宝贵的资源。 首先,ExtJS 4.1.1是该框架的一个稳定版本,发布于...

    ExtJs框架系列之filetree 源码

    - **事件监听**:FileTree中的各种交互行为(如点击、拖放、展开/折叠)都可通过事件监听机制来响应和处理。 深入研究这些源码,可以帮助开发者更好地掌握ExtJs框架的使用,尤其是对文件系统操作的需求。同时,通过...

    合同管理系统 extjs开发的 让大家一起学习

    5. **提醒与通知**:EXTJS支持事件监听和触发,可以实现合同到期、审批超时等重要事件的自动提醒。 在实际部署过程中,`setup.exe`是安装程序,用户可以通过运行这个文件来安装合同管理系统。`安装说明.txt`则提供...

    深入剖析ExtJS_2.2实现及应用

    《深入剖析ExtJS_2.2实现及应用》是一本专为高级Web开发者设计的书籍,专注于探讨...它提供的源码分析和实例应用相结合的学习路径,使得读者能在实践中深化理论,在理论中增强实践,从而达到"厚积薄发"的学习效果。

    深入浅出ExtJS随书源码--EXTJS2.0

    "深入浅出ExtJS随书源码--EXTJS2.0"是针对ExtJS 2.0版本的学习资源,通常与一本相关书籍配套,帮助读者通过实际代码加深对ExtJS的理解。 源码分析: 1. **组件系统**:ExtJS的核心是其组件模型,其中包括各种可...

    深入浅出extjs(第二版)随书源码

    该随书源码包含三个不同版本的ExtJS源码:ext-3.0.0、ext-3.1.1和ext-3.2.0。这涵盖了ExtJS 3.x的主要迭代,每个版本都可能包含新的特性和改进。通过学习这些源码,读者可以了解到ExtJS的历史演变,以及在不同版本间...

    ExtJs+java(SSH)项目源码

    **项目实战源码分析** 这个项目源码中,你将看到以下几个部分: 1. **前端部分**:主要使用ExtJs编写,包括各种页面布局、组件、交互逻辑。你可以在`js`目录下找到相关的JavaScript文件,这些文件定义了ExtJs组件和...

    ExtJS5学习之Grid与Grid之间的数据拖拽

    在"ExtJS5学习之Grid与Grid之间的数据拖拽"这个主题中,我们将深入探讨如何实现这种交互功能,以及背后的机制和重要知识点。 首先,我们要了解ExtJS的Grid组件。Grid是一种可配置的表格视图,它可以显示大量的结构...

    asp.net与extjs开发点卡在线销售系统完整源码

    《ASP.NET与EXTJS开发的点卡在线销售系统详解》 在互联网技术高速发展的今天,电子支付和在线交易已经成为日常生活的一部分。点卡作为一种便捷的预付费方式,广泛应用于游戏、软件激活、网络服务等多个领域。本文将...

Global site tag (gtag.js) - Google Analytics