stopPropagation: function() {var e = this.originalEvent;...if ( e.stopPropagation ) {e.stopPropagation();} jQuery重载stopPropagation函数调用的本地事件对象的stopPropagation函数阻止冒泡。也就是说,阻止冒泡的是当前节点,而不是事件源。event = event[ jQuery.expando ] ? event :new jQuery.Event( type, typeof event === "object" && event );2.修正浏览器事件(主要有修正事件源)和组合正确的事件处理参数data
if ( type.indexOf(".") >= 0 ) {//有命名空间的事件触发; 先取出事件处理入口函数handle()使用的事件类型typenamespaces = type.split(".");type = namespaces.shift();namespaces.sort();}...// 调用者可以传递jQuery.Event对象,普通对象,甚至是字符串event = event[ jQuery.expando ] ?event :new jQuery.Event( type, typeof event === "object" && event );event.isTrigger = true;event.namespace = namespaces.join(".");event.namespace_re = event.namespace ?new RegExp( "(^|\.)" + namespaces.join("\.(?:.*\.|)") + "(\.|$)" ) :null;// 重置result属性,避免上次的结果残留event.result = undefined;if ( !event.target ) {event.target = elem;}// 克隆传参data并将event放在传参data的前面,创建出事件处理入口函数的参数列表,创建后结果可能是[event,data]data = data == null ?[ event ] :jQuery.makeArray( data, [ event ] ); 后面这段组合事件处理参数列表data在后面处理时调用if ( handle ) {handle.apply( cur, data );} 3.判断是否是特殊节点对象的的特殊事件,是的话特殊处理 special = jQuery.event.special[ type ] || {}; if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { return; } 这里面需要特殊处理的事件比较少,这里列一下 special: {click.trigger: function(){ // checkbox, 触发本地事件确保状态正确if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {this.click();return false;}},focus.trigger: function() {// 触发本地事件保证失焦/聚焦序列正确if ( this !== document.activeElement && this.focus ) {try {this.focus();return false;} catch ( e ) {// Support: IE<9// If we error on focus to hidden element (#1486, #12518),// let .trigger() run the handlers}}},blur.trigger: function() {if ( this === document.activeElement && this.blur ) {this.blur();return false;}} } 4.从事件源开始遍历父节点直到Window对象,将经过的节点保存(保存到eventPath)下来备用for ( ; cur; cur = cur.parentNode ) { eventPath.push( cur ); tmp = cur;}// 将window也压入eventPath(e.g., 不是普通对象也不是断开连接的DOM)if ( tmp === (elem.ownerDocument || document) ) { eventPath.push( tmp.defaultView || tmp.parentWindow || window );} 5.循环先前保存的节点,访问节点缓存,如果有对应的事件类型处理队列则取出其绑定的事件(入口函数)进行调用。 // jQuery绑定函数处理:判断节点缓存中是否保存相应的事件处理函数,如果有则执行 handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // 本地绑定处理 handle = ontype && cur[ ontype ]; if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { event.preventDefault(); } 6. 最后处理浏览器默认事件,比如submit标签的提交表单处理。// 如果没有人阻止默认的处理,执行之if ( !onlyHandlers && !event.isDefaultPrevented() ) {...} 注意:普通事件加上命名空间仍然属于普通事件,普通调用方式依然其作用。比如$(document).on("click.chua","#id",fn1).on("click","#id",fn2);当点击“#id”节点的时候fn1依然会被调用。触发指定命名空间事件的唯一方式是trigger:$("#id").trigger("click.chua"),此时只会调用fn1。simulate: function( type, elem, event, bubble ) {// 构建一个新的事件以区别先前绑定的事件.// 新构建的事件避免阻止冒泡, 但如果模拟事件可以阻止默认操作的话,我们做同样的阻止默认操作。var e = jQuery.extend(new jQuery.Event(),event,{ type: type,isSimulated: true,originalEvent: {}});if ( bubble ) {jQuery.event.trigger( e, null, elem );} else {jQuery.event.dispatch.call( elem, e );}if ( e.isDefaultPrevented() ) {event.preventDefault();}} 看到没有,真正模拟冒泡函数是jQuery.event.trigger函数special: {load: {//阻止触发image.load事件冒泡到window.loadnoBubble: true},click: {//checkbox触发时保证状态正确trigger: function() {if (...) {this.click();return false;}}},focus: {//触发本当前节点blur/focus事件 确保队列正确trigger: function() {if ( this !== document.activeElement && this.focus ) {try {this.focus();return false;} catch ( e ) {// IE<9,如果我们错误的让隐藏的节点获取焦点(#1486, #12518),// 让.trigger()运行处理器}}},delegateType: "focusin"},blur: {trigger: function() {if ( this === document.activeElement && this.blur ) {this.blur();return false;}},delegateType: "focusout"},beforeunload: {postDispatch: function( event ) {//即使的returnValue等于undefined,Firefox仍然会显示警告 if ( event.result !== undefined ) {event.originalEvent.returnValue = event.result;}}}} focus/blur本来是不冒泡的,但是我们依然可以通过$(document).on("focus ","#left",fn)来绑定,是怎么做到的?我们来看jQuery的处理jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { var attaches = 0, handler = function( event ) { //模拟冒泡 jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); }; jQuery.event.special[ fix ] = {setup: function() {if ( attaches++ === 0 ) {document.addEventListener( orig, handler, true );}},teardown: function() {if ( --attaches === 0 ) {document.removeEventListener( orig, handler, true );}} };}); 再然后,就是绑定事件,绑定事件实际上就对focusin、focusout做了兼容处理,源码中第一个判断有special.setup.call(…)这段代码,根据上面setup函数可见第一次进入的时候实际上是通过setup函数中的document.addEventListener( orig, handler, true )绑定事件,注意:第一个参数是是orig,因为火狐不支持focusin/focusout所以jQuery使用focus/blur替代来监听事件;注意第三个参数是true,表示在事件捕获阶段触发事件。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 ); }}special第二组:mouseenter/mouseleave//使用mouseover/out和事件时机检测创建mouseenter/leave事件jQuery.each({mouseenter: "mouseover",mouseleave: "mouseout"}, function( orig, fix ) {jQuery.event.special[ orig ] = {delegateType: fix,bindType: fix,handle: function( event ) {var ret,target = this,related = event.relatedTarget,handleObj = event.handleObj;//对于mousenter/leave,当related在target外面的时候才调用handler//参考: 当鼠标离开/进入浏览器窗口的时候是没有relatedTarget的if ( !related || (related !== target && !jQuery.contains( target, related )) ) {event.type = handleObj.origType;ret = handleObj.handler.apply( this, arguments );event.type = fix;}return ret;}};}); 需要注意的是只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件。对应mouseleave这样的话,mouseenter子元素不会反复触发事件,否则在IE中经常有闪烁情况发生if ( !related || (related !== target && !jQuery.contains( target, related )) )其中!jQuery.contains( target, related )表示related在target外面。我们使用图例来解释

现在反过来,很明显related在target里面,那么鼠标之前就处于mouseenter状态(意味着之前就进行了mouseenter处理器处理),避免重复调用当然是不进行任何处理直接返回了。

我们假设处理的是mouseleave事件,离开target。
鼠标从target到related,很明显related在target里面,所以当鼠标移动到related的时候依然么有离开target,不做处理。

鼠标从target到related,很明显related在target外面,所以当鼠标移动到related的时候已经离开了target的范围,做处理。

special第三组:submit和change
主要是ie下submit不能冒泡的处理
jQuery.event.special.submit主要有一下几个特征
setup
postDispatch
teardown
根据添加事件的代码可知添加事件的时候如果符合条件则会调用setup来添加事件
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false )
jQuery在ie下模拟submit事件以click和keypress替代,只不过是添加了命名空间来区别和普通click和keypress事件。
setup: function() { ... jQuery.event.add( this, "click._submit keypress._submit", function( e ) { var elem = e.target, form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; if ( form && !jQuery._data( form, "submitBubbles" ) ) { jQuery.event.add( form, "submit._submit", function( event ) { event._submit_bubble = true; }); jQuery._data( form, "submitBubbles", true ); } });}, 在事件调用过程中(dispatch)会调用postDispatch来处理if ( special.postDispatch ) {special.postDispatch.call( this, event );} postDispatch中调用simulate完成事件处理postDispatch: function( event ) { // If form was submitted by the user, bubble the event up the tree if ( event._submit_bubble ) { delete event._submit_bubble; if ( this.parentNode && !event.isTrigger ) { jQuery.event.simulate( "submit", this.parentNode, event, true ); } }}, teardown用在删除事件绑定中ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); 主要源码如下 jQuery.event.special.change = {setup: function() {//rformElems = /^(?:input|select|textarea)$/iif ( rformElems.test( this.nodeName ) ) {// IE不会在check/radio失焦前触发change事件; 在属性更改后触发它的click事件// 在special.change.handle中会吞掉失焦触发的change事件.// 这里任然会在check/radio失焦后触发onchange事件.if ( this.type === "checkbox" || this.type === "radio" ) {jQuery.event.add( this, "propertychange._change", function( event ) {if ( event.originalEvent.propertyName === "checked" ) {this._just_changed = true;}});jQuery.event.add( this, "click._change", function( event ) {if ( this._just_changed && !event.isTrigger ) {this._just_changed = false;}// Allow triggered, simulated change events (#11500)jQuery.event.simulate( "change", this, event, true );});}return false;}// 事件代理; 懒惰模式为后代input节点添加change事件处理jQuery.event.add( this, "beforeactivate._change", function( e ) {var elem = e.target;if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {jQuery.event.add( elem, "change._change", function( event ) {if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {jQuery.event.simulate( "change", this.parentNode, event, true );}});jQuery._data( elem, "changeBubbles", true );}});},handle: function( event ) {var elem = event.target;// 吞掉本地单选框和复选框的change事件,我们在上面已经出发了事件if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {return event.handleObj.handler.apply( this, arguments );}}, } OK,到此,事件系统也告一个段落了,谢谢大家多多支持。