跨浏览器的事宜处置惩罚顺序完成总结

本文章须要一些前置学问

  1. 事宜基础学问

  2. event对象详解

围绕着怎样更好地完成一个跨浏览器的事宜处置惩罚小型库展开讨论。

1. 开端完成

在《JavaScript高等递次设计》中供应了一个EventUtil的对象,内里完成了一个跨浏览器的事宜绑定的API

var EventUtil = {
    addHandler : function (el, type, handler) {
        if(el.addEventListener) {
            el.addEventListener(type, handler, false);
        } else if (el.attachEvent)(
            el.attachEvent("on" + type, handler);
        ) else {
            el["on" + type] = handler;
        }
    },
    removeHandler : function (el, type, handler) {
        if(el.removeEventListener) {
            el.removeEventListener(type, handler);
        } else if (el.detachEvent) {
            el.detachEvent("on" + type, handler);
        } else {
            el["on" + type] = null;
        }
    }
}

这是完成实在较为的简朴直观,然则关于IE浏览器的处置惩罚实在有不好的处所,比方我们都晓得attachEvent()中的事宜处置惩罚递次会在全局作用域下实行,那末函数中的this就会指向window对象,这是一个题目,固然我们也能够对handler举行处置惩罚,绑定handler的函数作用域。另外,EventUtil并没有对event对象举行处置惩罚,因而传入handler的event也须要做兼容性处置惩罚,在封装方面做的就不好,编写handler时须要注重的处所就比较多。

var handler = function (event) {
    // 对event对象做兼容性处置惩罚,比方猎取target等
};

// 绑定函数作用域
handler = handler.bind(el); 

2. 更好的完成

下面是Dean Edward的完成,这也是jquery所自创的,扬弃掉attachEvent要领,直接运用跨浏览器的完成体式格局,即el.onXXX = handler,这类体式格局的肯定就是没法绑定多个,会举行掩盖,然则能够应用一些技能来填补。

// written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini
// http://dean.edwards.name/weblog/2005/10/add-event/

function addEvent(element, type, handler) {
    if (element.addEventListener) {
        element.addEventListener(type, handler, false);
    } else {
        // assign each event handler a unique ID
        if (!handler.$$guid) handler.$$guid = addEvent.guid++;
        // create a hash table of event types for the element
        if (!element.events) element.events = {};
        // create a hash table of event handlers for each element/event pair
        var handlers = element.events[type];
        if (!handlers) {
            handlers = element.events[type] = {};
            // store the existing event handler (if there is one)
            if (element["on" + type]) {
                handlers[0] = element["on" + type];
            }
        }
        // store the event handler in the hash table
        handlers[handler.$$guid] = handler;
        // assign a global event handler to do all the work
        element["on" + type] = handleEvent;
    }
};
// a counter used to create unique IDs
addEvent.guid = 1;

function removeEvent(element, type, handler) {
    if (element.removeEventListener) {
        element.removeEventListener(type, handler, false);
    } else {
        // delete the event handler from the hash table
        if (element.events && element.events[type]) {
            delete element.events[type][handler.$$guid];
        }
    }
};

function handleEvent(event) {
    var returnValue = true;
    // grab the event object (IE uses a global event object)
    event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
    // get a reference to the hash table of event handlers
    var handlers = this.events[event.type];
    // execute each event handler
    for (var i in handlers) {
        this.$$handleEvent = handlers[i];
        if (this.$$handleEvent(event) === false) {
            returnValue = false;
        }
    }
    return returnValue;
};

function fixEvent(event) {
    // add W3C standard event methods
    event.preventDefault = fixEvent.preventDefault;
    event.stopPropagation = fixEvent.stopPropagation;
    return event;
};
fixEvent.preventDefault = function() {
    this.returnValue = false;
};
fixEvent.stopPropagation = function() {
    this.cancelBubble = true;
};

这段代码实际上是对IE浏览器事宜绑定的一个修补,特别是旧版本的(IE8及更早的版本)。jquery自创了如许的一个思绪,写出了兼容各个浏览器的event模块。

3. jquery的完成思绪

在《JavaScript忍者秘笈》中,给出了一个越发高等的完成,他运用一个中心事宜处置惩罚递次,并将一切的处置惩罚递次都保留在一个零丁的对象上,最大化地掌握处置惩罚的历程,如许做有几个优点:

  • 范例处置惩罚递次的上下文,这个指的是作用域的题目,一般来讲,元素的事宜处置惩罚递次的上下文应当就是元素自身,即this === el为true。

  • 修复Event对象的属性,经由过程兼容性的处置惩罚,来到达与规范无异。

  • 处置惩罚渣滓接纳

  • 过滤触发或删除一些处置惩罚递次

  • 解绑特定范例的一切事宜

  • 克隆事宜处置惩罚递次

遵照如许的一个思绪,我们来一步步完成如许一个模块。

3.1 修复Event对象的属性

修复主要针对一些主要的属性举行修复,连系上一节的内容,有以下代码:

function fixEvent(event) {
    function returnTrue () {return true;}
    function returnFalse () {return false;}

    if(!event || !event.stopPropagation) { // 推断是不是须要修复
        var old = event || window.event; // IE的event从window对象中猎取

        event = {}; // 复制原有的event对象的属性

        for(var prop in old) {
            event[prop] = old[prop];
        }

        // 处置惩罚target
        if(!event.target) {
            event.target = event.srcElement || document;
        }

        // 处置惩罚relatedTarget
        event.relatedTarget = event.fromElement === event.target ? 
                                        event.toElement : 
                                        event.fromElement;
        
        // 处置惩罚preventDefault
        event.preventDefault = function () {
            event.returnValue = false;
            // 标识,event对象是不是挪用了preventDefault函数
            event.isDefaultPrevented = returnTrue; 
        }
        /*
            能够挪用event.isDefaultPrevented()来检察是不是挪用event.preventDefault
        */
        event.isDefaultPrevented = returnFalse; 

        event.stopPropagation = function () {
            event.cancelBubble = true;
            event.isPropagationStopped = returnTrue;
        }

        event.isPropagationStopped = returnFalse;

        // 阻挠事宜冒泡,而且阻挠实行其他的事宜处置惩罚递次
        // 借助标识位,能够在背面举行handlers行列处置惩罚的时刻运用
        event.stopImmediatePropagation = function () {
            event.isImmediatePropagationStopped = returnTrue;
            event.stopPropagation();
        }

        event.isImmediatePropagationStopped = returnFalse;

        // 鼠标坐标,返回文档坐标
        if(event.clientX != null){
            var doc = document.documentElement, body = document.body;

            event.pageX = event.clientX +
                (doc && doc.scrollLeft || body && body.scrollLeft || 0) - 
                (doc && doc.clientLeft || body && body.clientLeft || 0);
            
            event.pageY = event.clientY +
                (doc && doc.scrollTop || body && body.scrollTop || 0) -
                (doc && doc.clientTop || body && body.clientTop || 0);
        }
    
        event.which = event.charCode || event.keyCode;

        // 鼠标点击形式 left -> 0 middle -> 1 right -> 2
        if(event.button != null){
            event.button = (event.button & 1 ? 0 : 
                    (event.button & 4 ? 1 : 
                        (event.button & 2 ? 2 : 0)));
        }
    }
    return event;
}

3.2 中心对象保留dom元素信息

这个的目标是为了给元素竖立一个映照,标识元素和存储相关联的信息(事宜范例和对应的事宜处置惩罚递次),在jquery内里运用的是selector,在《JavaScript忍者秘笈》中,运用的是guid。

var cache = {},
    guidCounter = 1,
    expando = "data" + (new Date).getTime();

function getData(el) {
    var guid = el[expando];
    if(!guid){
        guid = el[expando] = guidCounter++;
        cache[guid] = {};
    }
    return cache[guid];
}

function removeData(el) {
    var guid = el[expando];
    if(!guid) return;
    delete cache[guid];
    try {
        delete el[expando];
    } catch(e){
        if(el.removeAttribute){
            el.removeAttribute(expando);
        }
    }
}

3.3 绑定事宜处置惩罚递次

var nextGuid = 1;

function addEvent(el, type, fn) {
    
    var data = getData(el);

    if(!data.handlers)data.handlers = {};

    if(!data.handlers[type])data.handlers[type] = [];

    // 给事宜处置惩罚递次给予guid,便于背面删除
    if(!fn.guid)fn.guid = nextGuid++;

    data.handlers[type].push(fn);

    // 为该元素的事宜绑定一致的回调处置惩罚递次
    if(!data.dispatcher) {
        // 是不是启用data.dispatcher
        data.disabled = false;
        data.dispatcher = function (event) {
            if(data.disabled)return;
            event = fixEvent(event);
            var handlers = data.handlers[event.type];
            if(handlers) {
                for(var i = 0, len = handlers.length; i < len; i++){
                    handlers[i].call(el, event);
                }
            }
        };
    }

    // 将一致的回调处置惩罚递次注册到,仅在第一次注册的时刻须要
    if(data.handlers.length === 1){
        if(el.addEventListener){
            el.addEventListener(type, data.dispatcher, false);
        } else (el.attachEvent) {
            el.attachEvent("on" + type, data.dispatcher);
        }
    }
}

3.4 清算资本

绑定了事宜,就还须要一个解绑事宜,由于我们运用的是托付处置惩罚递次来掌握处置惩罚流程,而不是直接绑定处置惩罚递次,所以也不能直接运用浏览器供应的解绑函数来处置惩罚。在这里,我们须要手动来清算一些资本,清算的递次从小到大。

function isEmpty(o){
    for(var prop in o){
        return false;
    }
    return true;
}

function tidyUp(el, type) {
    var data = getData(el);

    // 清算el的type事宜的回调递次
    if(data.handlers[type].length === 0) {
        delete data.handlers[type];

        if(el.removeEventListener){
            el.removeEventListener(type, data.dispatcher, false);
        } else if(el.detachEvent){
            el.detachEvent("on" + type, data.dispatcher);
        }
    }

    // 推断是不是另有其他范例的事宜处置惩罚递次,假如没有则进一步消灭
    if(isEmpty(data.handlers)){
        delete data.handlers;
        delete data.dispatcher;
    }

    // 推断是不是还须要data对象
    if(isEmpty(data)) {
        removeData(el);
    }
}

3.5 解绑事宜处置惩罚递次

为了尽量坚持天真,供应了以下的功用

  • 将一个元素的一切绑定事宜举行解绑

    removeEvent(el);
  • 将一个元素特定范例的一切事宜举行解绑

    removeEvent(el, "click");
  • 将一个元素的特定处置惩罚递次举行解绑

    removeEvent(el, "click", handler);
function removeEvent(el, type, fn) {
    var data = getData(el);

    if(!data.handlers)return;

    var removeType = function(t) {
        data.handlers[t] = [];
        tidyUp(el, t);
    };

    // 删除一切的处置惩罚递次
    if(!type){
        for(var t in data.handlers){
            removeType(t);
        }
        return;
    }

    var handlers = data.handlers[type];
    if(!handlers)return;

    // 删除特定范例的一切事宜处置惩罚递次
    if(!fn){
        removeType(type);
        return;
    }

    // 删除特定的事宜处置惩罚递次,这个时刻依据guid来举行删除
    // 这里须要斟酌的就是能够一个事宜处置惩罚递次被绑定到一个事宜范例屡次
    // 因而,这里须要用到handlers.length,删除的时刻,须要n--
    if(fn.guid) {
        for(var n = 0; n < handlers.length; n++){ 
            if(handlers[n].guid === fn.guid){
                handlers.splice(n--, 1);
            }
        }
    }

    // 返回之前举行资本清算
    tidyUp(el, type);
}

到这里,我们就获得一个既保证通用性又保证机能的事宜监听处置惩罚模块,但是事宜的学问并不仅仅这么一点,本章节的内容将会继承出现在接下来的几个小节,一同构建一个完全的event系统的代码库。

4. 参考

  1. 《JavaScript高等递次设计》

  2. 《JavaScript忍者秘笈》

  3. addEvent

5. 泉源

个人博客

    原文作者:zhangguixu
    原文地址: https://segmentfault.com/a/1190000007702605
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞