javascript事件监听机制(二)——jquery的Event对象

jQuery封装了浏览器的事件监听方法,兼容了各个浏览器的区别,对外提供一套适合于jquery对象的事件监听接口。Event对象的核心方法主要有三个,add,remove和trigger。

elem对于事件的维护,是通过在elem的内部空间里(_data访问的),用一个events对象来实现的

{
  events:{'click':handleObj, 'focusin':handleObj...},
  handle:function(e){this.elem = elem; 
                return function(e){bind(this.elem,arguments);}}
}

events对象用于维护该elem监听的全部事件。在events[type]下,是监听改时间的handle“材料包”。结构为:

handleObj = jQuery.extend({
				type: type, // 传入的类型,通过special适配之后可能变为其他的类型,如focus,转为focusin,blur转为focusout
				origType: tns[1],//初始类型 
				data: data, //传入的数据
				handler: handler, //callback们(通过extend实现的。再次绑定的时候,把新的selector和handler放到对应的位置)
				guid: handler.guid,//确保该elem对于这一类事件只有一个id
				selector: selector,//selector们
				namespace: namespaces.join(".") // 监听空间
			}, handleObjIn );

handler实质上是一个bind操作,执行回调,context为elem


三个操作都是围绕着这个数据结构进行操作。以下依次做分析:

add:

操作流程:

  
step1:判断节点类型,文本节点和属性节点不能添加事件,没有提供类型,回调,或者elem不是jq对象的话,直接返回
// Don't attach events to noData or text/comment nodes (allow plain objects tho)
		if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
			return; //预判,文本节点,属性节点不能附加事件
		}
step2:拆分handler,获取selector和callback。并将其整合为一个handleObjIn,用于和已有的handleObj做合并

if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

step3:构造该elem的events对象和handle。定义handle包裹为一个dispatch,本质上是一个bind函数
events = elemData.events;
		if ( !events ) {
			elemData.events = events = {}; //初始化elemData.events。第一次监听事件的情况,初始化events对象
		}
		eventHandle = elemData.handle;
		if ( !eventHandle ) { //初始化elemData.handle
			elemData.handle = eventHandle = function( e ) {
				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? //忽略trigger事件,否则调用dispatch方法。handle方法类似于一个代理,把真正的工作交给dispatch
					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
					undefined;
			};
			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
			eventHandle.elem = elem;
		}
step4:循环types,依次构造handlerObj

			tns = rtypenamespace.exec( types[t] ) || [];
			type = tns[1];
			namespaces = ( tns[2] || "" ).split( "." ).sort(); //拆分出来type和namespace

			// If event changes its type, use the special event handlers for the changed type
			// special In using this API, you can create custom events that do more than just execute bound event handlers when triggered—these “special” events can modify the event object passed to event handlers, trigger other entirely different events, or execute complex setup and teardown code when event handlers are bound to or unbound from elements.
			//special 不仅可以执行绑定到事件上的handler,还能修改事件对象,触发其他不同的事件。把一些神奇的事件放到special system里面
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			//如果有selector 用special决定的类型,或者绑定的类型。如果special失败用type。比如传入focus,会转化为focusin
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {}; //更新special 不懂为啥不直接在这里获取special对象

			// handleObj is passed to all event handlers
			//handler可以是一个函数,即绑定在事件上的方法
			//同时也可以是一个事件对象,也就是下面所说的handleObj,那么如果是在jQuery的内部是可以传递一个事件对象过来的
			handleObj = jQuery.extend({
				type: type,
				origType: tns[1],
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				namespace: namespaces.join(".")
			}, handleObjIn );

step5:绑定事件:

handlers = events[ type ]; //获取绑定的事件
			if ( !handlers ) {//如果还没有绑定的话,则初始化。handlers是因为可以对一个事件上绑定多个handlers
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;//事件委托数
                                //该元素首次监听该类事件的时候,先调用special的setup方法,然后如果失败了,那么手动进行事件绑定
				// Only use addEventListener/attachEvent if the special events handler returns false
				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // special方法失败之后,用add或者attach。 用load 和beforeunload事件。load用jquery提供的bindReady方法.beforeunload 需要elem是window
					// Bind the global event handler to the element
					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle, false );//显示的指明false,监听函数只在冒泡阶段被触发

					} else if ( elem.attachEvent ) {
						elem.attachEvent( "on" + type, eventHandle );
					}
				}
			}//setup总会在add之前执行
                        //调用special的add方法
			if ( special.add ) {//调用special的add方法
				special.add.call( elem, handleObj ); //把handleObj add到elem上

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// Add to the element's handler list, delegates in front
			if ( selector ) { //将handlerObj添加到elem的事件队列里面
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			//追踪哪个事件被触发了,用于优化
			jQuery.event.global[ type ] = true; // jquery.event.global会维护一个数组,用于存放全部监听的事件类型

最终执行完,events对象里面的数据存储结构为:

events={

  type+namespace:[handleobj1, handleobj2, handleobj3..],

  type2: [handleobj4, handleobj5...]

}


remove:

  操作流程:根据arguments提供的值,将target事件,或者事件组从events里面剥离下来。

 step1:先获取用户的events对象,如果没有,说明该对象没有绑定任何事件。直接返回

if ( !elemData || !(events = elemData.events) ) {//如果没有event,直接return
			return;
		}

 step2:先处理一下传入的types们,

types = jQuery.trim( hoverHack( types || "" ) ).split(" "); //获取传入的所有需要删除的types
其中hoverHack方法,目的在于将events里面的hover转为mouseenter mouseleave
hoverHack = function( events ) {
		return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
	};

在获取完所有的类型之后,核心是一个遍历。获取所有的事件类型

step3: 根据type,分割事件类型type+ 命名空间namespace。并获取events中type对应的空间。

special = jQuery.event.special[ type ] || {}; 
type = ( selector? special.delegateType : special.bindType ) || type; //如果有selector,则找到代理的type或者bindtype
eventType = events[ type ] || []; //获取绑定到这个事件上的一组handlers
origCount = eventType.length;
namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;

  • 获取该类型的special对象。
  • 根据是否提供了selector,调用specail的类型检查方法,或者直接是type
  • 根据type,获取events[type]对应的handleObj数组
step4:移除事件的操作
for ( j = 0; j < eventType.length; j++ ) {
				handleObj = eventType[ j ];// 获取每一个handleObj
				

                                        if ( ( mappedTypes || origType === handleObj.origType ) && 
                                        // 删除的条件是,type对的上号,命名空间对应,selector对上的,都满足的情况,才将其从events[type]中移除
					 ( !handler || handler.guid === handleObj.guid ) &&
					 ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
					 ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
                                        //需要绑定的事件类型,命名空间guid都对应的上才删掉
					eventType.splice( j--, 1 );

					if ( handleObj.selector ) {
						eventType.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

step5:最后需要判断删除完成之后,该类型是不是没有事件了。如果是的话,如果有specail,则调用specail的teardown,执行一些之前公共操作。否则直接调用delete方法,将该事件类型从events中删掉。

if ( eventType.length === 0 && origCount !== eventType.length ) {//如果该事件上没有handlers
				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ]; //删掉这个属性
			}

step6:如果删除完了之后,该elem没有再绑定任何事件。那么直接将events对象从elemData中删除
if ( jQuery.isEmptyObject( events ) ) {//这两个操作都是删掉events
			delete elemData.handle;

			// removeData also checks for emptiness and clears the expando if empty
			// so use it instead of delete
			jQuery.removeData( elem, "events", true ); //直接把events从elem上删掉
		}


trigger:

这个方法主要做的事情有:

1、适配参数,找到需要触发的event,触发元素elem等。

 2、构造event对象。

 3、根据事件触发的位置(捕获/冒泡),获取事件触发的路径eventPath

 4、根据eventPath,依次获取elemData.events[type] 中的handleObj,执行handleObj中的handle。并将data作为arguments传入。

 5、根据事件执行的结果,修正event中的属性。并将event.result返回


step1: 修正参数

// Don't do events on text and comment nodes
		if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
			return;
		}

		// Event object or event type
		var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
			type = event.type || event,//获取触发类型,可以通过obj传入,或者直接传入
			namespaces = [];

		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { //focus blur,focusin,focusout不能直接触发。测试 focusin+focus 和focusout+blur
			return;
		}

		if ( type.indexOf( "!" ) >= 0 ) {
			// Exclusive events trigger only for the exact event (no namespaces)
			type = type.slice(0, -1);
			exclusive = true;
		}

		if ( type.indexOf( "." ) >= 0 ) {//有命名空间的情况
			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split(".");
			type = namespaces.shift();
			namespaces.sort(); //多个空间的情况
		}
                
                //直接去global上看该事件是否被监听过,如果没有的话,直接返回
if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { // No jQuery handlers for this event type, and it can't have inline handlersreturn;}
 
 

 
 

step2:构造event对象

event = typeof event === "object" ? // 检测event是不是一个jquery的event对象。如果不是的话,重新构造
	// jQuery.Event object
	event[ jQuery.expando ] ? event :
	// Object literal
	new jQuery.Event( type, event ) :
	// Just the event type (string)
	new jQuery.Event( type );

		event.type = type;
		event.isTrigger = true;
		event.exclusive = exclusive;
		event.namespace = namespaces.join( "." );
		event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
		ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;//绑定event的对象到elem
		}
                data = data != null ? jQuery.makeArray( data ) : []; //如果传入了数据,那么先将数据构造为array
		data.unshift( event );//将传入的数据,绑定到event上


构造一个事件对象:

初始的构造为:

Event = {
   originalEvent:event对象,
   type:event.type即事件类型,
   timeStap: new date().getTime(),
   uid: true,
   isDefaultPrevented:function(){
      return getPreventDefault()? true:  false;
   }
}


经过trigger的加工之后,修正event.type 为type,添加isTrigger属性为true,namespace,ontype 为type中是否有:如果没有的话,则是ontype。event.result先定义为undefined。



step3:获取该type的special对象。先执行trigger前的前置操作。然后再获取事件触发路径eventPath。最终eventPath中的数据为二维数组,每一项为[elem,type]。

special = jQuery.event.special[ type ] || {};
		if ( special.trigger && special.trigger.apply( elem, data ) === false ) {//获取special,执行special的方法,如果返回false,那么不做后续操作
			return;
		}

		// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
		eventPath = [[ elem, special.bindType || type ]];//先选定好事件冒泡的顺序。
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {// 如果该事件允许冒泡,并且elem不是window

			bubbleType = special.delegateType || type;//获取冒泡的事件
			cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;//type是不是focus/blur。如果是的话,那么触发的元素转移到parent
			for ( old = elem; cur; cur = cur.parentNode ) {// 顺着当前元素向上,将elem和bubbletype放入eventPath中
				eventPath.push([ cur, bubbleType ]);
				old = cur;
			}//先获取路由顺序

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( old === (elem.ownerDocument || document) ) {
				eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);//到document之后,把window加到最后
			}
		}



step4: 顺着eventPath,依次触发事件。

// Fire handlers on the event path
		//在事件路径上触发事件
		for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {//如果没有发生事件拦截的话,一直向上冒泡

			cur = eventPath[i][0]; // 获取路径上的elem 和事件类型
 			event.type = eventPath[i][1];
                        //
                        //获取cur的elemData.event[type]上的handle
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) {handle.apply( cur, data ); // 执行handle}// Note that this is a bare JS function and not a jQuery handlerhandle = ontype && cur[ ontype ];//获取内联的handler // 如果该elem还有内联的方法,执行完结果为false没那么阻止之后的动作if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {event.preventDefault();//不要执行事件的默认动作}}event.type = type;
 
 

 
 

step5:依次触发完成之后,执行收尾动作

if ( !onlyHandlers && !event.isDefaultPrevented() ) {// 如果没有elem组织default action,那么在结束的时候,阻止之

			if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
				!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {

				// Call a native DOM method on the target with the same name name as the event.
				// Can't use an .isFunction() check here because IE6/7 fails that test.
				// Don't do default actions on window, that's where global variables be (#6170)
				// IE<9 dies on focus/blur to hidden element (#1486)
				if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") 
|| event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) 
{//如果有ontype,不是focus和blur,target可见,不是window

					// Don't re-trigger an onFOO event when we call its FOO() method
					old = elem[ ontype ];

					if ( old ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;//不要重复触发同样的事件
					elem[ type ]();
					jQuery.event.triggered = undefined;

					if ( old ) {
						elem[ ontype ] = old;
					}
				}
			}
		}

		return event.result;


  以上就是jquery事件处理机制的三个核心方法分析。之后将event的specail方法以及v8的事件处理机制。

猜你喜欢

转载自blog.csdn.net/u013237862/article/details/67639716