第一篇 http://www.cnblogs.com/aaronjs/p/3444874.htmljavascript
从上章就能得出几个信息:html
本章分析的重点:java
经过addEventListener触发事件后,回调句柄如何处理?node
具体来讲就是,如何委派事件的,用到哪些机制,咱们若是用到项目上是否能借鉴?jquery
涉及的处理:ajax
事件句柄的读取与处理api
事件对象的兼容,jQuery采起什么方式处理?数组
委托关系的处理浏览器
jQuery引入的处理方案缓存
jQuery.event.fix(event):将原生的事件对象 event 修正为一个 能够读读写event 对象,并对该 event 的属性以及方法统一接口。
jQuery.Event(event,props): 构造函数建立可读写的 jQuery事件对象 event, 该对象便可以是原生事件对象 event 的加强版,也能够是用户自定义事件
jQuery.event.handlers: 用来区分原生与委托事件
能学到的思路
缓存的分离
适配器模式的运用
事件兼容性的封装
委托的设计
事件的绑定执行顺序
结构
<div id='p1' style="width: 500px;height: 500px;background: #ccc"> <div id='p2' style="width: 300px;height: 300px;background: #a9ea00"> <p id="p3" style="width: 100px;height: 100px;background: red" id = "test"> <a id="p4" style="width: 50px;height: 50px;background:blue" id = "test">点击a元素</a> </p> </div> </div>
假如每个节点都绑定了事件,那么事件的触发顺序以下:
因而可知:
默认的触发循序是从事件源目标元素也就是event.target指定的元素,一直往上冒泡到document或者body,途经的元素上若是有对应的事件都会被依次触发
若是遇到委托处理?
看demo
最后获得的结论:
元素自己绑定事件的顺序处理机制
分几种状况:
假设绑定事件元素自己是A,委派元素B.C
第一种:
A,B,C各自绑定事件, 事件按照节点的冒泡层次触发
第二种:
元素A自己有事件,元素还须要委派元素B.C事件
委派的元素B.C确定是该元素A内部的,因此先处理内部的委派,最后处理自己的事件
第三种:
元素自己有事件,元素还须要委派事件,内部委派的元素还有本身的事件,这个有点绕
先执行B,C本身自己的事件,而后处理B,C委派的事件,最后处理A事件
为何须要了解这个处理的顺序呢? 由于jQuery作委托排序的时候要用到
既然能够冒泡,相应的也应该能够中止
事件对象提供了preventDefault,stopPropagation2个方法一个中止事件传播,一个传递默认的行为(暂且无视IE)
jQuery提供了个万能的 return false 不只能够阻止事件冒泡,还能够阻止浏览器的默认行为,还能够减小ie系列的bug。
其实就是根据返回的布尔值调用preventDefault,stopPropagation方法,下面会提到
e.stopImmediatePropagation方法不只阻止了一个事件的冒泡,也把这个元素上的其余绑定事件也阻止了
事件委托原理都知道,可是能有多少写得出jQuery这样的设计思路呢?好吧,若是您以为不须要,那么看看老是没坏处的。。。
先看看jQuery须要应对的几个问题
须要处理的的问题一:事件对象不一样浏览器的兼容性
event 对象是 JavaScript 中一个很是重要的对象,用来表示当前事件。event 对象的属性和方法包含了当前事件的状态。
当前事件,是指正在发生的事件;状态,是与事件有关的性质,如 引起事件的DOM元素、鼠标的状态、按下的键等等。
event 对象只在事件发生的过程当中才有效。
浏览器的实现差别:
获取event对象
特别指出:分析的版本是2.0.3,已经再也不兼容IE6-7-8了,因此部分兼容问题都已经统一了,例如:事件绑定的接口,事件对象的获取等等
事件对象具体有些什么方法属性参照 http://www.itxueyuan.org/view/6340.html
jQuery为dom处理而生,那么处理兼容的手段天然是独树一帜了,因此jQuery对事件的对象的兼容问题单独抽象出一个类,用来重写这个事件对象
jQuery 利用 jQuery.event.fix() 来解决跨浏览器的兼容性问题,统一接口。
除该核心方法外,统一接口还依赖于 (jQuery.event) props、 fixHooks、keyHooks、mouseHooks 等数据模块。
props 存储了原生事件对象 event 的通用属性
keyHook.props 存储键盘事件的特有属性
mouseHooks.props 存储鼠标事件的特有属性。
keyHooks.filter 和 mouseHooks.filter 两个方法分别用于修改键盘和鼠标事件的属性兼容性问题,用于统一接口。
好比 event.which 经过 event.charCode 或 event.keyCode 或 event.button 来标准化。
最后 fixHooks 对象用于缓存不一样事件所属的事件类别,好比
fixHooks['click'] === jQuery.event.mouseHooks;
fixHooks['keydown'] === jQuery.event.keyHooks;
fixHooks['focusin'] === {};
从源码处获取对事件对象的操做,调用jQuery.Event重写事件对象
// 将浏览器原生Event的属性赋值到新建立的jQuery.Event对象中去 event = new jQuery.Event( originalEvent );
event就是对原生事件对象的一个重写了,为何要这样,JQuery要增长本身的处理机制呗,这样更灵活,并且还能够传递data数据,也就是用户自定义的数据
先看看源码,如何处理事件对象兼容?
jQuery.Event构造函数
jQuery.Event = function( src, props ) { if ( src && src.type ) { this.originalEvent = src; this.type = src.type; this.isDefaultPrevented = ( src.defaultPrevented || src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; } else { this.type = src; } if ( props ) {jQuery.extend( this, props );} this.timeStamp = src && src.timeStamp || jQuery.now(); this[ jQuery.expando ] = true; };
方法
jQuery.Event.prototype = { isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse, preventDefault: function() { var e = this.originalEvent; this.isDefaultPrevented = returnTrue; if ( e && e.preventDefault ) {e.preventDefault();} }, stopPropagation: function() { var e = this.originalEvent; this.isPropagationStopped = returnTrue; if ( e && e.stopPropagation ) {e.stopPropagation(); } }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); } };
大致过目下,有个大概的轮毂,后面用了在具体分析
构造出来的新对象
看图,经过jQuery.Event构造器,仅仅只有一些定义的属性与方法,可是原生的事件对象的属性是否是丢了?
因此还须要把原生的的属性给混入到这个新对象上
那么此时带来一个问题,不一样事件会产生了不一样的事件对象,拥有不一样的属性,因此还的有一套适配的机制,根据不一样的触发点去适配须要混入的属性名
扩展经过jQuery.Event构造出的新事件对象属性
// 扩展事件属性 this.fixHooks[ type ] = fixHook = rmouseEvent.test( type ) ? this.mouseHooks : rkeyEvent.test( type ) ? this.keyHooks : {};
有一些属性是共用的,都存在,因此单独拿出来就行了
props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
而后把私有的与公共的拼接一下
copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
而后混入到这个新的对象上
jQuery本身写了一个基于native event的Event对象,而且把copy数组中对应的属性从native event中复制到本身的Event对象中
while ( i-- ) { prop = copy[ i ]; event[ prop ] = originalEvent[ prop ]; }
jQuery纠正了event.target对象
jQuery官方给的解释是,Cordova没有target对象
if ( !event.target ) { event.target = document; }
碰巧本人作的正是cordova项目
deviceready这个是设备准备就绪的事件,没有target
在最后jQuery还不忘放一个钩子,调用fixHook.fitler方法用以纠正一些特定的event属性
例如mouse event中的pageX,pageY,keyboard event中的which
进一步修正事件对象属性的兼容问题
fixHook.filter? fixHook.filter( event, originalEvent ) : event
fixHook就是在上一章,预处理的时候用到的,分解type存进去的,针对这个特性的单独处理
最后返回这个“全新的”Event对象
事件对象默认方法的重写
可见经过jQuery.Event构造出来的新的事件对象,就是对原生事件对象的一个增强版
重写了preventDefault,stopPropagation,stopImmediatePropagation等接口因为这些方法常常会被调用中,因此这里分析一下
取消特定事件的默认行为
preventDefault: function() { var e = this.originalEvent; this.isDefaultPrevented = returnTrue; if ( e && e.preventDefault ) { e.preventDefault(); } },
重写了preventDefault方法,可是现实上其实仍是调用浏览器提供的e.preventDefault方法的,惟一的处理就是增长了一个
状态机用来记录,当前是否调用过这个方法
this.isDefaultPrevented = returnTrue
一样的stopPropagation,stopImmediatePropagation都增长了 this.isPropagationStopped与 this.isImmediatePropagationStopped,
因此最后构造出来的新对象,既有原生的属性又多了不少自定义的属性方法~~ 这样jQuery能够用来玩花样了。。。
总的来讲jQuery.event.fix干的事情:
jQuery对事件体系的修正不止是作了属性兼容,重写了事件的方法,还增长状态机,那么这样的处理有什么做用?
须要处理的的问题二:数据缓存
jQuery.cache 实现注册事件处理程序的存储,实际上绑定在 DOM元素上的事件处理程序只有一个,即 jQuery.cache[elem[expando]].handle 中存储的函数,
因此只要在elem中取出当对应的prop编号去缓存中找到相对应的的事件句柄就行
这个简单了,数据缓存原本就提供接口
handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
事件句柄拿到了,是否是马上执行呢?固然不能够,委托还没处理呢?
须要处理的的问题三:区分事件类型,组成事件队列
事件的核心的处理来了,委托的重点
如何把回调句柄定位到当前的委托元素上面,若是有多个元素上绑定事件回调要如何处理?
作这个操做以前,根据冒泡的原理,咱们是否是应该把每个节点层次的事件给规划出来,每一个层次的依赖关系?
因此jQuery引入了jQuery.event.handlers用来区分普通事件与委托事件,造成一个有队列关系的组装事件处理包{elem, handlerObjs}的队列
在最开始引入add方法中增长delegateCount用来记录是否委托数,经过传入的selector判断,此刻就能派上用场了
先判断下是否要处理委托,找到委托的句柄
根据以前的测试demo,
在元素DIV下面嵌套了P,而后P内嵌套了A
此刻就要进入关键点了
分二种状况处理
第一种天然是没有委托,直接绑定的事件
body.on('click',function(){ alert('灰') })
由于selector不存在因此delegateCount === 0,
因此委托处理的判断不成立
if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
此时直接组装下返回elem与对应的handlers方法了
return handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
第二种就是委托处理
咱们取出固然绑定事件节点上的handlers,这个是在预分析的时候作的匹配关系,具体请看上一章
获得的处理关系
从图咱们能够得出
1 元素自己有事件
2 元素又要处理委托事件
那么事件的执行就须要有个前后,jQuery要如何排序呢?
依赖委托节点在DOM树的深度安排优先级,委托的DOM节点层次越深,其执行优先级越高
委托的事件处理程序相对于直接绑定的事件处理程序在队列的更前面,委托层次越深,该事件处理程序则越靠前。
源码的处理
if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { for ( ; cur !== this; cur = cur.parentNode || this ) { // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) if ( cur.disabled !== true || event.type !== "click" ) { matches = []; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) sel = handleObj.selector + " "; if ( matches[ sel ] === undefined ) { matches[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) >= 0 : jQuery.find( sel, this, null, [ cur ] ).length; } if ( matches[ sel ] ) { matches.push( handleObj ); } } if ( matches.length ) { handlerQueue.push({ elem: cur, handlers: matches }); } } } }
还有几个判断条件
若是有delegateCount,表明该事件是delegate类型的绑定
找出全部delegate的处理函数列队
火狐浏览器右键或者中键点击时,会错误地冒泡到document的click事件,而且stopPropagation也无效
if ( delegateCount && event.target.nodeType && (!event.button || event.type !== "click") ) {
在当前元素的父辈或者祖先辈有可能存在着事件绑定,根据冒泡的特性,咱们的依次从当前节点往上遍历一直到绑定事件的节点,取出每一个绑定事件的节点对应的事件处理器
for ( ; cur !== this; cur = cur.parentNode || this ) { //遍历节点
}
这里就有个cur === this 经过这个判断来处理是否为正确的委托的
这里要注意各问题
假如elem.on('click','p',function(){}),咱们在elem上点击,那么在elem的做用范围这个事件都会被触发到,若是此刻用于的目标不在P元素,可是又知足delegateCount存在
因此在cur===this,也就是目标对象就是elem了,那么判断此点击算无效点击,可是注意事件在绑定的区域内都每次触发都是会被执行的
遍历的过程须要过滤一些节点,好比disabled 属性规定应该禁用 input 元素,被禁用的 input 元素既不可用,也不可点击
if ( cur.disabled !== true || event.type !== "click" ) {
此时开始处理委托过滤的关系了
sel = handleObj.selector + " ";
咱们先肯定下在当前的上下文中是否能找到这个selector元素
这里用到了sizzle选择器去处理了
jQuery.find( sel, this, null, [ cur ] ).length;
若是能找到正确,是存在固然这个事件节点下面的元素,就是说这个节点是须要委托处理的
一样的的组成一个handlerQueue
handlerQueue.push({ elem: cur, handlers: matches });
根据demo点击a元素,会冒泡到P 最后到div,属于handlerQueue就有a与p的处理器了
从这里咱们能够看出delegate绑定的事件和普通绑定的事件是如何分开的。
对应一个元素,一个event.type的事件处理对象队列在缓存里只有一个。
按照冒泡的执行顺序,与元素的从内向外递归,以及handlers的排序,因此就处理了
因此就造成了事件队列的委托在前,自身事件在后的顺序,这样也跟浏览器事件执行的顺序一致了
区分delegate绑定和普通绑定的方法是:delegate绑定从队列头部推入,而普通绑定从尾部推入,经过记录delegateCount来划分,delegate绑定和普通绑定。
总的来讲jQuery.event.handlers干的事情:
将有序地返回当前事件所需执行的全部事件处理程序。
这里的事件处理程序既包括直接绑定在该元素上的事件处理程序,也包括利用冒泡机制委托在该元素的事件处理程序(委托机制依赖于 selector)。
在返回这些事件处理程序时,委托的事件处理程序相对于直接绑定的事件处理程序在队列的更前面,委托层次越深,该事件处理程序则越靠前。
返回的结果是 [{elem: currentElem, handlers: handlerlist}, ...] 。
事件句柄缓存分析了
事件对象兼容分析了
委托关系分析了
在从头看看事件执行的流程
绑定
elem.addEventListener( type, eventHandle, false );
事件句柄
eventHandle = elemData.handle = function( e ) { return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; };
这里其实用jQuery.event.dispatch.call就能够了,传递只是一个事件对象,而后this指向了这个事件元素elem
直接传递:jQuery.event.dispatch.call( eventHandle.elem, e) 这样不更直接吗?
call的性能在某些浏览器下要明显比apply好,而其余浏览器中二者差异不大
dispatch事件分发器源码
dispatch: function( event ) { event=
jQuery.event.fix( event ); var i, j, ret, matched, handleObj, handlerQueue = [], args = core_slice.call( arguments ), handlers= ( data_priv.get( this, "events" ) || {} )[ event.type ] ||
[], special = jQuery.event.special[ event.type ] || {}; args[0] = event; event.delegateTarget = this; if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } handlerQueue= jQuery.event.handlers.call( this
, event, handlers ); i = 0; while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem;j = 0; while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); if ( ret !== undefined ) { if ( (event.result = ret) === false ) { event.preventDefault(); event.stopPropagation(); } } } } } if ( special.postDispatch ) { special.postDispatch.call( this, event ); } return event.result; },
dispatch事件分发器
可见依次处理了上面文章因此讲的三个问题
1,2与步都只作修饰性的处理,关键是handlers方法,咱们从中取得了handlerQueue队列
贴一下对handlerQueue事件队列的处理方法
while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem; j = 0; while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); if ( ret !== undefined ) { if ( (event.result = ret) === false ) { event.preventDefault(); event.stopPropagation(); } } } } }
这个代码就是针对handlerQueue的筛选了
1 最开始就分析的事件的执行顺序,因此handlerQueue彻底是按照事件的顺序排列的,委托在前,自己的事件在后面
2 产生的事件对象其实只有一份,经过jQuery.Event构造出来的event
在遍历handlerQueue的时候修改了
事件是绑定在父节点上的,因此此时的目标节点要经过替换,还有相对应的传递的数据,与处理句柄
event.currentTarget = matched.elem;
event.handleObj = handleObj;
event.data = handleObj.data;
3 执行事件句柄
ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args);
4 若是有返回值 好比return false
系统就调用
event.preventDefault();
event.stopPropagation();
根据上面的分析咱们就能很好的分析出on的执行流程了
在p1上绑定了自身事件,同事绑定了委托事件到li a p上都触发,而后都调用同一个回调处理
var p1 = $('#p1') p1.on('click',function(){ console.log('灰') }) p1.on('click','li,a,p',function(e){ console.log(e) })
处理的流程:
使用jQuery处理委托的优点?
从以上的分析咱们不难看,jQuery对于事件的处理是极其复杂的
那么jQuery 事件委托机制相对于浏览器默认的委托事件机制而言,有什么优点?
不难发现其优点在于委托的事件处理程序在执行时,其内部的 this 指向发出委托的元素(即知足 selector 的元素),而不是被委托的元素,
记得吧
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
.apply( matched.elem, args );
jQuery 在内部认为该事件处理程序仍是绑定在那个发出委托的元素上,所以,若是开发人员在这个事件程序中中断了事件扩散—— stopPropagation,那么后面的事件将不能执行。
固然还要涉及自定义事件,事件模拟,trigger与事件销毁,在慢慢写吧。。
文字挺多,我的看法难免有误,欢迎你们指出~~
若是以为还能够,就顺手推荐下吧~