zoukankan      html  css  js  c++  java
  • 读Ext之四(事件的低级封装)

    这篇开始读ext-base-event.js。该文件定义了Ext.lib.Event对象,Ext.lib这个命名空间在Ext core的Ext.js中命名的。

    Ext.ns("Ext.util", "Ext.lib", "Ext.data");
    

    Ext.lib上的属性如下:

    Ext.lib.Ajax
    Ext.lib.Anim
    Ext.lib.AnimMgr
    Ext.lib.Bezier
    Ext.lib.Dom
    Ext.lib.Easing
    Ext.lib.Event
    Ext.lib.AnimBase
    Ext.lib.ColorAnim
    Ext.lib.Motion
    Ext.lib.Scroll



    Ext.lib.Event 是Ext中事件处理的轻度封装,概览下

    Ext.lib.Event = function() {
        var loadComplete = false,
        ...
        ...
    	return pub;
    }();
    

    可以发现仍然是一个匿名函数执行,执行后返回对象pub,pub赋值给Ext.lib.Event。再看内部细节

    var loadComplete = false,
        unloadListeners = {},
        retryCount = 0,
        onAvailStack = [],
        _interval,
        locked = false,
        win = window,
        doc = document,
    
        // constants
        POLL_RETRYS = 200,
        POLL_INTERVAL = 20,
        EL = 0,
        TYPE = 0,
        FN = 1,
        WFN = 2,
        OBJ = 2,
        ADJ_SCOPE = 3,
        SCROLLLEFT = 'scrollLeft',
        SCROLLTOP = 'scrollTop',
        UNLOAD = 'unload',
        MOUSEOVER = 'mouseover',
        MOUSEOUT = 'mouseout',
    

    以上定义了一堆变量。window,document对象分别赋值给了win,doc。这样做的好处是减少了一层闭包。使用局部变量win,doc比直接使用window,document要快。因为它们存在于执行函数的活动对象中,解析标识符只需要查找作用域链中的单个对象。
    而读取变量值的耗时是随着查找作用域链的逐层深入而不断增加。这点可参考:《JS权威指南》第五版4.7节:深入理解变量作用域。

    doc后是一堆常量定义,Ext的编码习惯亦是常量全部使用大写,有多个单词时用下划线连接。接下来是一堆私有方法/函数定义,即这些函数只能在上面提到的最外层的匿名函数内使用。

    // private
    doAdd = function() {
        var ret;
        if (win.addEventListener) {
            ret = function(el, eventName, fn, capture) {
                if (eventName == 'mouseenter') {
                    fn = fn.createInterceptor(checkRelatedTarget);
                    el.addEventListener(MOUSEOVER, fn, (capture));
                } else if (eventName == 'mouseleave') {
                    fn = fn.createInterceptor(checkRelatedTarget);
                    el.addEventListener(MOUSEOUT, fn, (capture));
                } else {
                    el.addEventListener(eventName, fn, (capture));
                }
                return fn;
            };
        } else if (win.attachEvent) {
            ret = function(el, eventName, fn, capture) {
                el.attachEvent("on" + eventName, fn);
                return fn;
            };
        } else {
            ret = function(){};
        }
        return ret;
    }(),
    

    doAdd,亦是一个匿名函数执行后返回新函数,用来给html元素添加事件及事件响应函数(handler)。这个函数和多数的事件添加函数差不多,用特性判断 。标准浏览器使用addEventListener添加,IE系列使用attachEvent,都不支持则返回一个空函数。这里有几点,
    1,有的代码中使用特性判断时,先写win.attachEvent,后是win.addEventListener。这是不对的,应该优先使用标准的addEventListener,而IE9同时支持这两种方式。
    2,这里新增了mouseenter /mouseleave 事件,它们仅IE支持。mouseenter不同于mouseover,它是在第一次鼠标进入节点区域时触发,以后在节点区域内(子节点间)移动时不触发。Goodbye mouseover, hello mouseenter 详细讲述了使用mouseenter的好处。此处有简单的实现

    这里为非IE浏览器间接实现了这两个事件,需要另两个函数的辅助

    function checkRelatedTarget(e) {
        return !elContains(e.currentTarget, pub.getRelatedTarget(e));
    }
    function elContains(parent, child) {
       if(parent && parent.firstChild){
         while(child) {
            if(child === parent) {
                return true;
            }
            child = child.parentNode;
            if(child && (child.nodeType != 1)) {
                child = null;
            }
          }
        }
        return false;
    }
    

    elContains 两个参数parent,child判断某个元素child是否是parent的子元素,是则返回true,否则false。
    checkRelatedTarget 会作为一个拦截器,这里e.currentTarget IE6/7/8不支持。pub.getRelatedTarget(e)是下面封装好的方法,IE中使用fromElement,toElement。

    fn = fn.createInterceptor(checkRelatedTarget);
    

    实现的基本思路:使用mouseover事件,即当给某元素(parent)添加mouseenter事件时,鼠标移至parent时触发事件handler,但从其子元素上移动时并不触发。

    顺便提下,Ext这里的elContains方法的实现明显欠妥,实际上IE中可以使用contains ,现代浏览器则可使用compareDocumentPosition ,谢谢天堂 提醒。John 写了个

    function contains(a, b){
      return a.contains ?
        a != b && a.contains(b) :
        !!(a.compareDocumentPosition(b) & 16);
    }
    

    jQuery的选择器Sizzle.contains也是这么实现。

     

    function getScroll() {
        var dd = doc.documentElement,
            db = doc.body;
        if(dd && (dd[SCROLLTOP] || dd[SCROLLLEFT])){
            return [dd[SCROLLLEFT], dd[SCROLLTOP]];
        }else if(db){
            return [db[SCROLLLEFT], db[SCROLLTOP]];
        }else{
            return [0, 0];
        }
    }
    

    私有的getScroll方法返回文档的scrollTop和scrollLeft值,由于浏览器差异,该实现上先从document.documentElement取,为0后再从document.body上取。都没有返回[0,0]。

     

    function getPageCoord (ev, xy) {
        ev = ev.browserEvent || ev;
        var coord  = ev['page' + xy];
        if (!coord && coord !== 0) {
            coord = ev['client' + xy] || 0;
            if (Ext.isIE) {
                coord += getScroll()[xy == "X" ? 0 : 1];
            }
        }
        return coord;
    } 
    

    私有的getPageCoord方法用来获取鼠标事件时相对于文档的坐标(水平,垂直)。

    Firefox引入了pageX / Y ,IE9/Safari/Chrome/Opera虽然支持但仅在文档(document)内而非页面(page)。

    Safari/Chrome/Opera可以使用标准的clientX/Y获取,IE下可通过clientX/Y与scrollLeft/scrollTop计算得到。
    IE9实际上也可通过clientX/Y获取,这里判断浏览器Ext.isIE在IE9正式版即将发布后明显欠妥。

    再往下就是一个对象pub,匿名函数执行后会返回该对象。猜测pub是public的简写,即匿名函数执行后对外公开的接口对象(pub)。pub有以下方法

    addListener: function(el, eventName, fn) {
        el = Ext.getDom(el);
        if (el && fn) {
            if (eventName == UNLOAD) {
                if (unloadListeners[el.id] === undefined) {
                    unloadListeners[el.id] = [];
                }
                unloadListeners[el.id].push([eventName, fn]);
                return fn;
            }
            return doAdd(el, eventName, fn, false);
        }
        return false;
    },
    

    为元素添加事件,el为添加事件的元素,eventName为事件名称(如click),fn为响应函数(hanlder)。对“unload”事件做了单独处理,内部调用私有的doAdd函数。

     

    removeListener: function(el, eventName, fn) {
        el = Ext.getDom(el);
        var i, len, li, lis;
        if (el && fn) {
            if(eventName == UNLOAD){
                if((lis = unloadListeners[el.id]) !== undefined){
                    for(i = 0, len = lis.length; i < len; i++){
                        if((li = lis[i]) && li[TYPE] == eventName && li[FN] == fn){
                            unloadListeners[id].splice(i, 1);
                        }
                    }
                }
                return;
            }
            doRemove(el, eventName, fn, false);
        }
    },
    

    删除元素已注册的事件响应函数,参数同addListener。

    这两个函数都有个注释:This function should ALWAYS be called from Ext.EventManager
    可以发现,真正客户端程序员在使用Ext库时并不直接使用Ext.lib.Event.addListener / Ext.lib.Event.removeListener添加或删除事件。

    而是使用Ext.EventManager.addListener / Ext.EventManager.removeListener或者它们的缩写Ext.EventManager.on / Ext.EventManager.un。
    Ext.EventManager对事件管理提供了更高层次的封装。后续会介绍。

    getTarget : function(ev) {
        ev = ev.browserEvent || ev;
        return this.resolveTextNode(ev.target || ev.srcElement);
    },
    

    获取事件源对象。W3C标准使用 target ,IE6/7/8使用了专有的 srcElement 。令人惊奇的是Safari/Chrome/Opera也支持IE6/7/8方式,即同时支持标准和IE专有方式。Firefox仅支持标准的target,IE9beta现已支持target。

     

    getRelatedTarget : function(ev) {
        ev = ev.browserEvent || ev;
        return this.resolveTextNode(ev.relatedTarget ||
                (ev.type == MOUSEOUT ? ev.toElement :
                 ev.type == MOUSEOVER ? ev.fromElement : null));
    },
    

    获取事件相关的元素。W3C标准使用 relatedTarget ,IE6/7/8使用了专有的 fromElement / toElement 。同样Safari/Chrome/Opera也支持IE6/7/8方式,即同时支持标准和IE专有方式。Firefox仅支持标准的relatedTarget,IE9也已支持relatedTarget。

     

    getPageX : function(ev) {
        return getPageCoord(ev, "X");
    },
    getPageY : function(ev) {
        return getPageCoord(ev, "Y");
    },
    getXY : function(ev) {
        return [this.getPageX(ev), this.getPageY(ev)];
    },
    

    getPageX,getPageY调用私有的getPageCoord,getPageCoord介绍如上。getXY调用getPageX,getPageY。

     

    stopEvent : function(ev) {
        this.stopPropagation(ev);
        this.preventDefault(ev);
    },
    stopPropagation : function(ev) {
        ev = ev.browserEvent || ev;
        if (ev.stopPropagation) {
            ev.stopPropagation();
        } else {
            ev.cancelBubble = true;
        }
    },
    preventDefault : function(ev) {
        ev = ev.browserEvent || ev;
        if (ev.preventDefault) {
            ev.preventDefault();
        } else {
            ev.returnValue = false;
        }
    },
    

    这三个方法反过来说,即先说preventDefault,阻止元素的默认行为。如链接A点击,默认会跳转;input[type=submit]点击,默认会提交表单。
    W3C标准使用 preventDefault 方法,IE6/7/8则是设置 returnValue 为false。Safari/Chrome/Opera同时支持IE6/7/8方式。Firefox仅支持标准的preventDefault。IE9现已支持preventDefault。

    stopPropagation 用来停止事件冒泡。W3C标准使用stopPropagation,IE6/7/8则是设置 cancelBubble 为true。
    Safari/Chrome/Opera/Firefox也支持IE方式取消冒泡。目前为止这是Firefox唯一的一个支持IE方式的属性。IE9beta现已支持stopPropagation。

    stopEvent则同时阻止默认行为和事件冒泡。

     

    getEvent : function(e) {
        e = e || win.event;
        if (!e) {
            var c = this.getEvent.caller;
            while (c) {
                e = c.arguments[0];
                if (e && Event == e.constructor) {
                    break;
                }
                c = c.caller;
            }
        }
        return e;
    },
    

    getEvent顾名思义获取事件对象。W3C标准使用响应函数的第一个参数获取,IE6/7/8则使用window.event获取。
    Safari/Chrome/Opera也支持IE6/7/8方式获取,IE9beta已支持W3C标准方式获取。
    关于各种情形下事件对象的获取见:获取事件对象的全家

    getCharCode : function(ev) {
        ev = ev.browserEvent || ev;
        return ev.charCode || ev.keyCode || 0;
    },
    

    获取按键码,注意在keypress 事件中使用。键盘事件DOM2中压根没有标准化,见:Key events 
    因此各浏览器自行实现,Firefox/Safari/Chrome/IE9beta支持charCode,IE6/7/8/Opera不支持但使用keyCode替代。

     

    getListeners : function(el, eventName) {
        Ext.EventManager.getListeners(el, eventName);
    },
    
    // deprecated, call from EventManager
    purgeElement : function(el, recurse, eventName) {
        Ext.EventManager.purgeElement(el, recurse, eventName);
    },
    

    这两个方法在后续讲述。

    再下对load, unload做了单独处理。

    Ext.lib.Event完毕。

     

    ext-base-event.js

  • 相关阅读:
    Binary Tree
    (并查集..含小总结)A
    (超简单并查集)POJ2524 Ubiquitous Religions(8.4.4)
    OpenSees开发(一)windows 上编译opensees (使用vs2005)
    vector的push_back对于拷贝构造和赋值操作的调用
    一个指针的引用引发的血案
    MFC中由左键单击模拟左键双击引起的问题
    CLAPACK动态调用
    SVN部署(远程)客户端篇
    SVN部署(本地)
  • 原文地址:https://www.cnblogs.com/snandy/p/2470555.html
Copyright © 2011-2022 走看看