11.事件系统

事件系统是一个框架很是重要的部分,用于响应用户的各类行为。
浏览器提供了3个层次的api,用于响应用户的各类行为。css


1.最原始的是写在元素标签内。
2.再次是脚本内,以el.onXXX = function绑定的方式,统称为DOM0事件系统。
3.最后是多投事件系统,一个元素的同一类型事件能够绑定多个回调,统常称为DOM2事件系统。html

因为浏览器大战,现存两套API。node

IE与opera:
绑定事件:el.attachEvent("on"+ type, callback)
卸载事件:el.detachEvent("on"+ type. callback)
建立事件:el.document.createEventObject()
派发事件:el.fireEvent(type,event)jquery

w3c:git

绑定事件:el.addEventListener(type,callback,[phase])
卸载事件:el.removeEventListener(type,callback,[phase])
建立事件:el.createEvent(types)
初始化事件:event.initEvent()
派发事件:el.dispatchEvent(event)github

从api的数量与形式来看,w3c提供的复杂不少,相对于也强大不少,下面咱们将逐一分析web

首先咱们先来几个简单的例子,不必动用框架。不过事实上,整个事件系统就创建在它们的基础上。chrome

复制代码
    function addEvent (el, callback, useCapture) {
        if(el.dispatchEvent){//w3c优先
            el.addEventListener(type, callback, !!useCapture );
        } else {
            el.attachEvent( "on"+ type, callback );
        }
        return callback; //返回callback方便卸载时用
    }

    function removeEvent (el, type, callback, useCapture) {
        if (el.dispatchEvent) { //w3c优先
            el.removeEventListener (type, callback, !!useCapture);
        } else {
            el.detachEvent( "on"+type, callback )
        }
    }

    function fireEvent (el, type, args, event) {
        args = args || {}
        if (el.dispatchEvent) { //w3c优先
            event = document.createEvent("HTMLEvents");
            event.initEvent(type, true, true);
        } else {
            event = document.createEventObject();
        }

        for (var i in args) if (args.hasOwnProperty(i)) {
            event[i] = args[i]
        }

        if (el.dispatchEvent) {
            el.dispatchEvent(event);
        } else {
            el.fireEvent('on'+type , event)
        }
    }
复制代码

一,onXXX绑定方式的缺陷编程

onXXX既能够写在html标签内,也能够独立出来,做为元素节点的一个特殊属性来处理,不过做为一个古老的绑定方式,它很难预料到人们对这方面的扩展。bootstrap

总结下来有如下不足:

1.onXXX对DOM3新增的事件或FF某些私有实现没法支持,主要有如下事件:

DOMActivate
DOMAttrModified
DOMAttributeNameChanged
DOMCharacterDataModified
DOMContentLoaded
DOMElementNameChanged
DOMFocusIn
DOMFocusOut
DOMMouseScroll
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDcouemnt
DOMSubtreeModified
MozMousePixelScroll

2.onXXX只容许元素每次绑定一个回调,重复绑定冲掉以前的绑定
3.onXXX在IE下回调没有参数,在其余浏览器回调的第一个参数是事件对象。
4.onXXX只能在冒泡阶段可用。

二,attachEvent的缺陷

attachEvent是微软在IE5添加的API,Opera也支持,也对于onXXX方式,它能够容许同一种元素同一种事件绑定多个回调,也就是所谓多投事件机制。但带来的麻烦只多很多,存在如下几点缺陷。

1.ie下只支持微软的事件系统,DOM3事件一律不支持。
2.IE下attchEvent回调中的this不是指向被绑定元素,而是window!
3.IE下同种事件绑定多个回调时,回调并非按照绑定时的顺序依次触发的!
4.IE下event事件对象与w3c的存在太多差别了,有的没法对上号,好比currentTarget
5.IE仍是只支持冒泡阶段。


关于事件对象,w3c是大势所趋,在IE9支持W3c那一套API时,这对咱们实现事件代理很是有帮助。

三,addEventListener的缺陷

w3c这一套API也不是至善至美,毕竟标准老是滞后于现实,剩下的标准浏览器各有本身的算盘,它们之间也有不一致的地方。

1.新事件很是不稳定,可能还有普及就开始被废弃,在早期的sizzle选择器引擎中,有这么几句。

    document.addEventListener("DOMAttrModified", invalidate, false);
    document.addEventListener("DOMNodeInserted", invalidate, false);
    document.addEventListener("DOMNodeRemoved", invalidate, false);

如今这三个事件被废弃了(准确的说,全部变更事件都完蛋了),FF14和chrome18开始使用MutationObserver代替它。

2.Firefox不支持focusin,focus事件,也不支持DOMFocusIn,DOMFocusOut,如今也不肯意用mouseWheel代替DOMMouseScroll。chrome不支持mouseenter与mouseleave.

所以,不要觉得标准浏览器就确定实现了w3c标准事件,全部特征侦测必不可少。

3.第三个,第四个,第五个标准参数。

第三个参数,useCapture只有很是新的浏览器才是可选项。好比FF6或以前是可选的,为了安全起见,确保第三个参数为布尔。

4.事件成员的不稳定。
w3c是从浏览器商抄过来的,人家用了这么久,不免与标准不一致。

ff下event.timeStamp返回0的问题,这个bug,2004年就有人提交了,直到2011年才被修复。

Safari下event.target可能返回文本节点

event.defaultPrevented,event.isTrusted与stopImmediatePropagation方法,以前标准浏览器都统一用getpreventDefault方法作这个事情,在jQuery源码中,发现它是用isDefaultPrevented来处理。

isTrusted属性用于表示当前事件是不是由用户行为触发,好比是用一个真实的鼠标点击触发click事件,仍是由一个脚本生成的(使用事件构造方法,好比event.initEvent)。isTrusted请多关注

5.标准浏览器没有办法模拟像IE6-8的proprtychange事件。

虽然标准的浏览器有input, DOMAttrModified,MutationObserver,但比起propertychange弱爆了。propertychange能够监听多种属性变化,而不仅仅是value值。另外它不区分attribute和property。所以,不管是经过el.xxx = yyy 仍是el.setAttribute(xxx,yyy)都接触此事件。

http://www.cnblogs.com/rubylouvre/archive/2012/05/26/2519263.html (判断浏览器是否支持DOMAttrModified)

四,Dean Edward的addEvent.js源码分析

这是一个prototype时代早期出现的一个事件系统。jQuery事件系统源头。亮点以下:

1.有意识的屏蔽IE与w3c在阻止默认行为与事件传播接口的差别。
2.处理ie执行回调时的顺序问题
3.处理ie的this指向问题
4.没有平台检测代码,由于是使用最通用最原始的onXXX构建
5.彻底跨浏览器(IE4与NS4)。

此处省略源码分析

http://dean.edwards.name/weblog/2005/10/add-event/

复制代码
// 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;
};
复制代码

不过在Dean Edward对应的博文中就能够看到许多指正与有用的patch。好比说,既然全部的修正都是冲着IE去的,那么标准浏览器用addEventListener就行。有的还提到,在iframe中点击事件时,事件对象不对的问题,提交如下有用的补丁。

    event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);


其中,第54条回复,直接致使了jQuery数据缓存系统的产生,为了不交错引用产出的内存泄露,建议元素分配一个uuid,全部的回调都放在一个对象中存储。

但随着事件的推移,使用者发现onXXX在IE存在不可消除和弥补的内存泄露,所以,翻看jQuery早期的版本,1.01是照抄Dean Edward的,1.1.31版本,开始吸取54条uuid的建议,并使用attach/removeEventListener绑定事件——每一个元素只绑定一次。而后全部回调都在相似handleEvent的函数中调用。

五,jQuery1.8.2的事件模块概述

jQuery的事件模块发端于Dean Edward的addEvent,而后它不断吸取社区的插件与补丁,发展成为很是棒的事件系统。其中不得不提的是事件代理与事件派发机制。


早在07年,Brandon Aaron为jQuery写了一个划时代的插件,叫livequery,它能够监听后来插入的元素的事件。好比说,一个表格,咱们为tr元素绑定了mouseover/mouseout事件时,只有十行代码,而后咱们又动态加载了20行,这20个tr元素一样能执行mouseover/mouseout回调。魔术在于,它并无把事件侦探器绑定在tr元素上,而是绑定在最顶层的document上,而后经过事件冒泡,取得事件源,断定它是否匹配给用户给定的css表达式,才执行用户回调、具体参考它的github:

https://github.com/brandonaaron/livequery

若是一个表格有100个 tr元素,每一个都要绑定mouseover/mouseout事件,改为事件代理的方式,能够节省99次绑定,这优化很好,更况且它能监听未来添加的元素,所以被立马吸取到jquery1.3中去,成为它的live方法,再把一些明显的bug修复了。jquery1.32成为最受欢迎的版本。与后来的jquery1.42都是历程碑式的。 

不过话说回来,live方法须要对某些不冒泡的事件作些处理,好比一些表单事件,有的只能冒泡的form,有的只能冒泡到document,有的根本就不冒泡。

  ie6 ie8 ff3.6 opera10 chome4 sfari4
submit form form document document document document
reset form form document document form form
change 不冒泡 不冒泡 document document 不冒泡 不冒泡
click document document document document document document
select 不冒泡 不冒泡 document document 不冒泡 不冒泡

对于focus, blur, change, submit, reset, select等不会冒泡的事件,在标准浏览器中,咱们咱们能够设置addEventListener最后的一个参数为true轻松搞定,在IE就有点麻烦了,要用focusin代替focus,focusout代替blur,selectsatrt代替select,change和submit,reset就更复杂了。必须使用其余的事件来模拟,还要判断事件源的类型,slelectedIndex, keyCode等相关属性。

这个课题到最后由一个叫reglib的库搞定,reglib的做者还写过一篇很著名的文章,《goodbuy mouseover,hello mouseenter》,来推广微软系统的两个事件,mouseenter与mouseleave。jQuery全面接纳了他们。

live方法带来的全新体验是空前的,但毕竟要冒泡到最顶层,对IE来讲有点坎坷,还会失灵。最好能指定父节点,一个绑定时已经存在的父节点。这样就不用费力了。当时有三篇博文给出了相近的方案,他们给出的接口一篇比一篇接近jhhon Resig接纳的方案。

http://danwebb.net/2008/2/8/event-delegation-made-easy-in-jquery

http://raxanpdi.com/blog-jquery-event-delegate.html (jQuery Event Delegation)

http://blog.threedubmedia.com/2008/10/jquerydelegate.html(已经打不开)

那时,它已是这个样子

    $('div').delegate('click','span',function (event) {
        $(this).toggleClass('selected');
        return false;
    })

并提出解除代理的API:undelegate

在jquery1.42在2010年2月19推出时,也这两个接口,前面的两个参数只是掉换一下。

    $('div').delegate('span','click',function( event ){
        $( this ).toggleClass('selected');
        return false;
    })

正所众人拾柴火焰高,jquery强大无不道理,在jquery1.8中,它又吸取dperini/nwevents的点子,改进其代理事件,大大 提升了性能。

先看一下其主要接口:

Jquery事件接口:bind live delegate on one trigger hover unbind die undelegate off toggle triggerHander Event .

其中bind,unbind,one,trigger,toggle,hover,ready一开始就有。

triggerHandler是jQuery1.23增长的,内部依赖于trigger,只对当前匹配元素的第一个有效,不冒泡不触发默认行为。

live与die是jQuery1.3增长的,用于事件代理,统一由document代理

delegate与undelegate是jquery1.4增长的,容许指定代理元素的事件代理,它内部是利用live,die实现的。

on与off是jQuery1.7增长的,目的是统一事件接口,bind,one,live,delegate,直接由on衍生,unbind,die,dundelegate直接由off衍生。

hover用于模拟css的hover效果,内部依赖于mouseenter,mouseleave

ready能够看作是load事件的加强版,获取最先的DOM可用后,当即执行各类dom操做。

toggle是click的加强版,每次点击都执行不一样的回调,并切换到下一个。

triggle与triggleHander是jQuery的fireEvent实现

 

此外,jQuery还有25个事件类型命名的快捷方法,当参数个数为2时表现为绑定事件。个数为0时表现为派发事件

jQuery快捷事件:contextmenu, error, keyup, keypress, keydown, submit, select, change, mouseleave, mouseenter, mouseout, mouseover, blur, focus, focusin, focusout, load, resize, scroll, unload, click, dbclik,mousedown, mouseup, mousemove

不过,最重要最基础的设施无疑是jQuery.event之下

add方法用于绑定事件。

remove方法用于卸载事件。

dispath方法用于统一用户回调,至关于Dean Edward的handleEvent方法,所以,jQuery1.7前它的名字一直叫handle。

trigger用于事件派发。

fix用于修正事件对象。

自jQuery1.4起,jQuery大大强化自定义事件功能。special对象原本是用于修正个别事件的,如今它容许这些事件经过setup, teardown, add, remove, default这些接口实现DOM事件的各类行为

参考:

http://benalman.com/news/2010/03/jquery-special-events/

六,jQuery.event.add源码解读

add方法的主要目的,将用户全部的传递参数,合并成一个handleObj对象放到元素对应的缓存体中events对象的某个队列中,而后绑定一个回调。这个回调会处理用户的全部回调。所以,对于每一个元素的每一种事件,它只绑定一次

复制代码
    var add = function(elem, types, handler, data, selector) {
        var elemData, eventHandle, events, t, tns, namespaces, handleObj;
        //若是elem不能添加自定义属性,因为ie下访问文本节点会报错,所以事件源不能是文本节点。
        //注释节点原本就不该该绑定事件,注释节点之因此混进来,是由于jQuery的html方法所致
        //若是没有指定事件的类型或回调也当即返回,再也不向下操做
        if (elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data(elem))) {
            return;
        }
        //取得用户回调与css表达式,handleObjIn这种结构咱们称为事件描述
        //记下用户绑定此回调的各类信息,方便用于“事件拷贝”
        if (handler.handler) {
            handlerObjIn = handler;
            handler = handlerObjIn.handler;
            selector = handlerObjIn.selector;
        }
        //确保回调有uuid,用于查找和移除
        if (!handler.guid) {
            handler.guid = jQuery.guid++;
        }

        //为元素在数据缓存系统中开辟一个叫"event"的空间来保存其全部回调与事件处理器
        events = elemData.events;
        if (!events) {
            elemData.events = events = {};
        }
        eventsHandle = elemData.handle; //事件处理器
        if(eventHandle) {
            elemData.handle = eventHandle = function(e) {
                //用户在事件冒充时,被第二次fire或者页面的unload后触发
                return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventsHandle.elem, arguments) : undefined;
            };
            //原注释是说,防止IE下非原生的事件内存泄露,(直接影响是明确了this的指向)
            eventHandle.elem  = elem;
        }
        //经过空格隔开同时绑定多个事件,好比jQuery(...).bind("mouseover mouseout", fn);
        types = jQuery.trim(hoverHack(types)).split(" ");
        for (t = 0 ; t < types.length; t++) {
            tns = rtypenamespace.exec(types[t]) || [] ; //取得命名空间
            type = tnsp[1];//取得真正的事件
            namespaces = (tns[2] || "").split(".").sort();//修正命名空间
            //并非全部事件都能直接使用,好比FF下没有mousewheel,须要用DOMMouseScroll冒充
            special = jQuery.event.special[ type ] || {};
            //有时候,咱们只须要在事件代理时进行冒充,好比FF下的focus, blur
            type = (selector ? special.delegateType : special.bindType) || type;

            special = jQuery.event.special[type] || {};
            //构建一个事件描述对象
            handleObj = jQuery.extend({
                type : type,
                origType : tns[1],
                data : data,
                handler : handler,
                guid : handler.guid,
                selector : selector,
                needsContext : selector && jQuery.expr.match.needsContext.test(selector),
                namespace : namespaces.join(".")
            },handlerObjIn);

            //在events对象上分门别类的存储事件描述,每种事件对于一个数组
            //每种事只绑定一次监听器(既addEventListener, attachEvent)
            handlers = events [type];
            if (!handlers) {
                handlers = events[ type ] = [];
                handlers.delegateCount = 0; //记录要处理的回调个数
                //若是存在special.setup而且special.setup返回0个才直接使用多投事件API
                if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
                    if (elem.addEventListener) {
                        elem.addEventListener(type, eventHandle, false);
                    } else if (elem.attachEvent) {
                        elem.attachEvent("on" + type, eventHandle);
                    }
                }
            }
            if (special.add) { //处理自定义事件
                special.add.call(elem, handleObj);
                if (!handleObj.handler.guid) {
                    handleObj.handler.guid = handler.guid;
                }
            }
            //add to the element's hanlder list, deleate in front
            if (selector) { //若是是事件代理,那么把此事件代理描述放在数据的前面
                handlers.splice(handlers.delegateCount++, 0, handleObj);
            } else {
                handlers.push(handleObj);
            }
            //用于jQuery.event.trigger,若是此事件从未绑定过,也不必进入trigger的真正处理逻辑
            jQuery.event.golbal[ type ] = true;
        }
        //防止ie内存泄露
        elem = null
    }
复制代码

从上面的注释中咱们能够得出,jQuery的回调再也不直接与元素挂钩,而是经过uuid访问数据缓存系统,抵达对于的events对象,再根据事件类型获得一组事件描述。而且事件描述里边没有事件源的记录,所以,很是方便导出挪动,为事件克隆大开方便之门。固然,其中数据缓存系统是关键,接着探索,就会发现,其它事件代理部分对数据缓存依赖的更严重。

下面是元素数缓存与事件描述之间的结构

在firebug下查看结构:以下

 

七,jQuery.event.remove的源码解读

remove方法的主要目的是,根据用户传参,找到事件队列,从里边把匹配的handleObj对象移除,在参数不足的状况,可能移除N个或全部。当队列的长度为零就移除事件,当events为空对象,则清除掉UUID

复制代码
    remove = function(elem, types, handler, selector) {
        var t, tns, type, origType,namespaces, origCount, j, events, special, eventType, handleObj,
        elemData = jQuery.hasData( elem ) && jQuery._data( elem );
        //若是不支持添加自定义属性或没有缓存与事件有关的东西,当即返回。
        if ( !elemData || !(events = elemData.events)) {
            return;
        }
        //hover转换为"mouseenter mouseleave",而且按照空格进行切割,方便移除多种事件类型
        types = jQuery.trim( hoverHack( types || "")).split(" ");
        for (t = 0; t < types.length; t++) {
            tns = rtypenamespace.exec(types[t]) || [];
            type = origType = tns[1]; //取得事件类型
            namespaces = tns[2];//取得命名空间
            if (!type) { //若是没有指定事件类型,则移除全部事件类型或移除全部与此命名空间相关的事件类型
                for (type in events) {
                    jQuery.event.remove( elem, type + types[t], handler, selector, true);
                }
                continue;
            }
            //利用事件冒充,取得真正的用于绑定事件类型
            special = jQuery.event.special[ type ] || {};
            type = (selector ? special.delegateType : special.bindType) || type;
            eventType = events[type] || []; //取得装载事件描绘对象的数组
            origCount = eventType.length;
            //取得用于过滤命名空间的正则,没有为null
            namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)" : null ;

                //移除符合条件的事件描述
                for ( j = 0; j < eventType.length; j++) {
                    handleObj = eventType[ j];
                    if (( origType === handleObj.guid ) & //比较事件类型是否一致
                        (!handler || handler.guid === handleObj.guid) && //若是传递了回调,断定UUid是否相同
                        //若是types含义命名空间,用正则看是否匹配
                        //若是事件代理必有css表达式,比较与事件描述对象中是否相等
                        (!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) {
                        eventType.splice(j-- , 1); //是就移除
                        if ( handleObj.selector ) { //同时delegateCount减去1
                            eventType.delegateCount--;
                        } 

                        if (special.remove) { //处理个别事件移除
                            special.remove.call( elem, handleObj);
                        }
                    }
                }
                //若是已经移除全部此类问题,则卸载框架绑定去的elemData.handle
                //origCount !== eventType.length 是为了防止死循环
                if ( eventType.length === 0 && origCount !== eventType.length) {
                    if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle ) === false) {
                        jQuery.removeEvent (elem, type, elemData.handle); 
                    }
                    delete events[ type ];
                }
        }
        //若是events为空,则从elemData中删除events与handler
        if ( jQuery.isEmptyObject( events )) {
            delete elemData.handle;
            jQuery.removeData (elem, "events", true)
        }
    }
复制代码


事件卸载部分是jQuery事件系统中最简单的部分,主要逻辑都花在移除事件描述对象和匹配条件上。

八,jQuery.event.dispatch的源码解读

这是jQuery事件系统的核心。它就是利用这个dispatch方法,从缓存体中的events对象取得对于的队列,而后修复事件对象,逐个传入用户的回调中执行,根据返回值决定是否断开循环(stopImmediatePropagation),阻止默认行为和事件传播。

 

复制代码
    dispatch = function(event) {
        //建立一个伪事件对象(jQuery.Event实列),从真正的实际对象上抽得响应的属性附于其上
        //若是是iE,也能够将其转换成对于的W3c属性,抹去两大平台的差别。
        event = jQuery.event.fix(event || window.event);

        var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
        //取得全部事件描述对象
             handlers = ((jQuery._data(this, "events") || {}) [ event.type ] || []),
            delegateCount = handlers.delegateCount,
            args = core_slice.call(arguments),
            run_all = !event.exclusive && !event.namespace,
            special = jQuery.event.special[ event.type ] || {},
            handlerQueue = [];
        //重置第一个参数为jQuery.Event实例
        args[0] = event;
        event.delegateTarget = this; //添加一我的为的属性,用于事件代理
        //执行preDispatch回调,它与后面的postDispatch构成一种相似AOP的机制
        if (special.preDispatch && special.preDispatch.call(this, event) === false) {
            return;
        }

        //若是是事件代理,而且不是来自非左键的点击事件
        if (delegateCount && !(event.button && event.type === "click")) {
            //从事件源开始,遍历其全部祖先一直到绑定事件的元素
            for ( cur = event.target; cur != this; cur = cur.parentNode || this) {
                //不要触发被disabled元素的点击事件
                if (cur.disabled !== tru event.type !== "click") {
                    selMatch = {}; //为了节能起见,每种CSS表达式只断定一次,经过下面的
                    //jQuery( sel, this).index( cur ) >= 0 或 jQuery.find(sel, this, null, [ cur ].length)
                    matches = []; //用于收集符合条件的事件描述对象
                    //使用事件代理的事件描述对象老是排在最前面
                    for (i = 0; i < delegateCount; i++ ) {
                        handleObj = handler[ i ];
                        sel = handleObj.selector;

                        if (selMatch[ sel ] === undefined) {
                            //有多少个元素匹配就收集多少个事件描述对象
                            selMatch[ sel] = handleObj.needsContext ? jQuery(sel, this).index(cur) >= 0 : jQuery.find(sel, this, null, [cur]).length;
                        }
                        if (selMatch[ sel ]){
                            matches.push(handleObj);
                        }
                    }
                    if (matches.length) {
                        handlerQueue.push({elem: cur, matches : matches});
                    }
                }
            }
        }
        //取得其余直接绑定的事件描述对象
        if (handlers.length > delegateCount) {
            handlerQueue.push({elem : this, matches : handlers.slice(delegateCount) })
        }

        //注意:这个循环是从下到上执行的
        for (i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++) {
            matched = handlerQueue[ i ];
            event.currentTarget = matched.elem;
            //执行此元素的全部与event.type同类型的回调,除非用户调用了stopImmediatePropagation方法,它会致使isImmediatePropagetionStopped返回true,从而中断循环
            for (j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++) {
                handleObj = matched.matches[j];
                //最后的过滤条件为事件命名空间,好比著名的bootstrap的命名空间为data-api
                if (run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test(handleObj.namespace)) {
                    event.data = handleObj.data;
                    event.handleObj = handleObj;
                    //执行用户回调,(有时 可能还要外包一层,来自jQuery.event.specal[type].handle)
                    ret = ((jQuery.event.special[ handleObj.origType ] || {}).handle || 
                        handleObj.handler).apply(matched.elem, args);
                    //根据结果断定是否阻止事件传播与默认行为
                    if (ret !== undefined) {
                        event.resulet = ret;
                        if (ret === false) {
                            //
                            event.preventDefault();
                            event.stopPropagation();
                        }
                    }
                }
            }
        }
        //执行postDispatch回调
        if (special.postDispatch) {
            special.postDispatch.call(this, event);
        }
        return event.resulet;
    }
复制代码

本节的难点在于如何模拟事件的传播机制,jQuery实际上只模拟冒泡那一阶段。

九,jQuery.event.trigger解读

笼统的来讲,triggter就是dispatch的增强版。

dispatch只触发当前元素与其底下元素(经过事件代理的方式)的回调,trigger则模拟整个冒泡过程,除了它自身,还触发其祖先节点与window的同类型的回调。不过,从trigger的代码来看,它比dispatch多作的事情就是触发事件的默认行为。

这涉及到太多的断定,若是再把dispatch的代码写在一块就很差维护了。

可是,我以为,trigger其实用不着那么复杂,trigger要作的事情就是在某一元素触发一个回调(dispatch),生产一个事件对象,而后让其顺序冒泡,触发其它的回调(dispatch)就好了。

浏览器提供了原生的派发机制,IE下的fireEvent,标准浏览器为dispatchEvent.

若是在控制台执行如下函数,咱们会获得更多的事件构造器

    Object.getOwnPropertyNames(window).filter(function (p) {
        return typeof window[p] == "function" && (window[p].prototype instanceof Event)
    })
复制代码
["MIDIMessageEvent", "MIDIConnectionEvent", "MediaKeyMessageEvent", "MediaEncryptedEvent", "webkitSpeechRecognitionEvent", "webkitSpeechRecognitionError", "StorageEvent", "SpeechSynthesisEvent", "MediaStreamEvent", "IDBVersionChangeEvent", "GamepadEvent", "DeviceOrientationEvent", "DeviceMotionEvent", "CloseEvent", "OfflineAudioCompletionEvent", "AudioProcessingEvent", "MediaKeyEvent", "XMLHttpRequestProgressEvent", "WheelEvent", "WebGLContextEvent", "UIEvent", "TransitionEvent", "TrackEvent", "TouchEvent", "TextEvent", "SecurityPolicyViolationEvent", "SVGZoomEvent", "ProgressEvent", "PopStateEvent", "PageTransitionEvent", "MutationEvent", "MouseEvent", "MessageEvent", "MediaQueryListEvent", "KeyboardEvent", "HashChangeEvent", "FocusEvent", "ErrorEvent", "CustomEvent", "CompositionEvent", "ClipboardEvent", "BeforeUnloadEvent", "AutocompleteErrorEvent", "ApplicationCacheErrorEvent", "AnimationEvent", "WebKitAnimationEvent", "WebKitTransitionEvent"]
复制代码

(webkit包含)

但经常使用的交互都集中在HTML4.0已经定义好的事件上,咱们无需理会message storage popstate事件,更别提事件变更。咱们认为支持如下事件就行了

HTMLEvents Load, unload, abort, error, select, change, submit, reset, focus, blur, resize, scroll
KeyboardEvent keypress keydown keyup
MouseEvents contextmenu, click, dblclick, mouseout, mouseover, mouseenter, mouseleave, mousemove,mousedown, mouseup,mousewheel

根据上面的表格,咱们作一个叫eventMap的hash出来,那么trigger方法最大限制级能够压缩成以下样子:

复制代码
    trigger: function (type, target) {
        var doc = target.ownerDocument || target.document || target || document ;
        event = doc.createEvent(eventMap[type] || "CustomEvent");
        if (/^(focus|blur|select|submit|reset)$/.test(type)) {
            target[type] && target[type] (); //触发默认行为
        }
        Event.initEvent( type, true, true);
        target.dispatchEvent(event);
    }
复制代码


可是这样trigger出来的对象是只读,不能覆盖原生属性或方法,所以,你能够觉得它自定义一个more属性,里边装载着你要改的东西,而后在dispatch将它包装成一个jQuery伪事件对象后,在把循环加在伪事件对象就行 了。

十,jQuery对事件对象的修复(不更新)

十一,滚轮事件的修复(不更新)

十二,mouseenter与mouseleave事件的修复(不更新)

十三,focus与focusout事件的修复(不更新)

十四,旧版本下IE的submit的事件代理实现(不更新)

在旧版本的IE下submit不会冒泡到顶层,它只执行form元素的submit回调,并当即执行提交跳转,所以只能用事件冒充的方式来实现。

咱们看看什么状况下浏览器触发submit事件吧。submit事件与鼠标事件,键盘事件是不同的。它是一种复合事件,既可使用鼠标实现,也能够经过键盘事件实现,重要的结果,能实现表单提交便可。

当焦点聚焦于input[type=text]、input[type=password]、input[type=checkbox]、input[type=radio]、input[type=button]、input[type=image]、input[type=submit]、按回车键就会触发提交

 

当鼠标在input[type=image],input[type=submit]上方,经过点击事件就会触发提交
浏览器差别,IE下能够在input=file回车后触发
咱们也可使用form.submit()这样的编程手段触发

IE下submit, reset的事件代理,delegate用于检测它有没有用事件代理,不用管它。重点在于它在代理元素上一下绑定了两个事件,click,keypress,若是是键盘事件,就判断他的keycode是否为13108(回车键)

el.submit()方法有个特异之处,它是不会执行submit回调的,像其余click,blur,focus,select这样的DOM方法都会同时执行回调与默认行为。

 

 

十五,oninput事件的兼容性性处理(不更新)

相关文章
相关标签/搜索