Event源码

Event模块的设计思路

1、$.event.add(elem,types,handler,data,selector) 添加事件回调函数:

构造Data对象实现,其中,elemData.handle属性存放的回调函数通过elem.addEventListener方法绑定到元素上,页面事件触发时,执行该回调函数;elemData.events属性以对象形式存放事件类型、命名空间、捕获元素、回调的guid、回调函数的其他信息等。同时在$.event.dispatch()方法中,elemData.events对象用于生成elemData.handle回调函数的内部代码。

因为Data对象附着在elem元素上,可通过dataPriv.get(this,"events")方式获取,并进行后续使用、添加或移除操作。

// 添加绑定事件
	add:function(elem,types,handler,data,selector){

		var handleObjIn, eventHandle, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData=dataPriv.get(elem);// 创建或获取Data对象,附着在jquery对象elem的属性中

		if ( !elemData ){// 文本、注释节点不能设置Data对象
			return;
		}

		if ( handler.handler ){// 回调函数设置为对象形式,handler属性为函数,selector为捕获节点
			handleObjIn=handler;
			handler=handleObjIn.handler;
			selector=handleObjIn.selector;
		}

		// jQuery.find.matchesSelector(elem,expr) 判断元素elem是否满足表达式expr
		if ( selector ){
			jQuery.find.matchesSelector(documentElement,selector);
		}

		// 绑定函数添加guid属性,以便解绑时确认删除绑定事件
		if ( !handler.guid ){
			handler.guid=jQuery.guid++;
		}

		// elemData.events未定义设置为空对象,通过events获取elemData.events;elemData.handle相同
		// elemData.events[type]存储handleObj,包含事件类型、上下文、选择器、回调函数相关数据
		// hanlers获取elemData.events[type]添加回调函数
		// elemData.handle存储回调函数的触发方法dispatch,接着通过elemData.events获取回调
		if ( !(events=elemData.events) ){
			events=elemData.events={};
		}
		if ( !(eventHandle=elemData.handle) ){
			// jQuery.event.dispatch获取原生语句addEventListener绑定的回调函数,并且添加到Data对象中
			// 传参为on语法回调函数的参数arguments,event对象内部构
			eventHandle=elemData.handle=function(e){

				// 页面尚未完成加载或者调用$ele.trigger()方法时,不用原生语句添加事件
				return typeof jQuery!=="undefined" && jQuery.event.triggered!==e.type ?
					jQuery.event.dispatch.apply(elem,arguments) : undefined;
			};
		}

		types=( types || "" ).match(rnotwhite) || [""];// 多事件以空格分割
		t=types.length;
		while (t--){
			// 事件类型配置项拆分所属事件类型名和命名空间
			tmp=rtypenamespace.exec(types[t]) || [];
			type=origType=tmp[1];// 事件类型
			namespaces=( tmp[2] || "" ).split(".").sort();// 命名空间

			if ( !type ){
				continue;
			}

			special=jQuery.event.special[type] || {};// 特殊事件focusin等经jquery转化成focus输出

			// 获取浏览器原生的事件focusin、focusout等
			type=(selector ? special.delegateType : special.bindType) || type;

			special=jQuery.event.special[type] || {};

			// handleObj包含事件类型、回调函数等信息,存储在elemData.events[type]数组中
			handleObj=jQuery.extend({
				type:type,// 浏览器支持的事件类型
				origType:origType,// jquery支持的事件类型
				data:data,// 附带的数据
				handler:handler,// 事件的回调函数
				guid:handler.guid,// 回调函数的guid
				selector:selector,// 捕获的节点
				needsContext:selector && jQuery.expr.match.needsContext.test(selector),
				namespace:namespaces.join(".")// 命名空间
			},handleObjIn);// handleObjIn回调函数相关数据

			// elemData.events[type]未定义设置为空数组,通过handlers获取elemData.events[type]
			// type为浏览器原生事件类型
			if ( !(handlers=events[type]) ){
				handlers=events[type]=[];
				handlers.delegateCount=0;

				// special.setup方法以使focus、blur事件通过向文档节点挂载$ele.trigger实现获得、失去焦点
				// addEventListener原生语句绑定事件,事件捕获功能由浏览器原生语句支持
				if ( !special.setup || special.setup.call(elem,data,namespaces,eventHandle)===false ){

					if ( elem.addEventListener ){// 浏览器原生语句绑定事件
						elem.addEventListener(type,eventHandle);
					}
				}
			}

			// 默认special.add均为false,add方法对Data对象中回调队列进行改写
			if ( special.add ){
				special.add.call(elem,handleObj);

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

			// 有捕获元素,回调队列前插入,无则队列后添加;调用回调时dispatch、handlers方法获取handlers
			if ( selector ){
				handlers.splice(handlers.delegateCount++,0,handleObj);
			} else {
				handlers.push(handleObj);
			}

			jQuery.event.global[type]=true;
		}
	},

2、$.event.dispatch(nativeEvent) 构造元素绑定的回调函数:

通过Dom接口addEventLIstener添加到elem元素上,当浏览器点击、悬停等事件触发时执行回调函数。

构造回调函数过程中,首先浏览器原生事件对象nativeEvent通过jQuery.event.fix方法封装为jQuery特有的event对象,且作为回调的首参;其余参数在add方法调用dispatch时带入。

回调函数执行前先执行sepcial.preDispatch函数,执行完成后执行sepcial.postDispatch函数。

通过$.event.handlers获取触发事件元素及其父元素的data对象上绑定的回调函数和执行上下文,通过event.isPropagationStoped方法判断是否阻止向上冒泡,以及event.isImmediatePropagationStoped方法旁观是否阻止同一元素的其他挂载函数,否则通过比较命名空间执行特殊的sepcial[origType].handle回调、或者$.event.handlers取出的回调,回调返回为false时阻止默认事件以及向上冒泡。

// 事件触发时获取回调函数队列并处理执行
	// 首先执行有捕获元素selector设置的回调函数,再执行没有捕获元素的回调函数
	// event.stopPropagation阻止向上冒泡触发事件,event.stopImmediatePropagation阻止元素后续的绑定事件
	// dispatch方法内部this关键字为绑定事件元素
	dispatch:function(nativeEvent){

		// 获取jquery包装的event对象
		var event=jQuery.event.fix(nativeEvent);

		var i, j, ret, matched, handleObj, handlerQueue,
			args=new Array(arguments.length),
			handlers=( dataPriv.get(this,"events") || {} )[event.type] || [],// 获取绑定函数
			special=jQuery.event.special[event.type] || {};

		args[0]=event;

		for ( i=1; i<arguments.length; i++ ) {
			args[i]=arguments[i];// 用户配置的回调函数参数
		}

		event.delegateTarget=this;// 绑定事件的节点

		// special.preDispatch、special.postDispatch钩子进行预处理、回调后处理
		if ( special.preDispatch && special.preDispatch.call(this,event)===false ){
			return;
		}

		// 获取Data对象中回调函数队列,及回调函数执行的上下文
		handlerQueue=jQuery.event.handlers.call(this,event,handlers);

		i=0;
		// event.isPropagationStopped返回真值阻止事件冒泡,只执行捕获或绑定元素的事件
		while ( ( matched=handlerQueue[i++] ) && !event.isPropagationStopped() ){
			event.currentTarget=matched.elem;

			j=0;
			// event.isImmediatePropagationStopped返回真值阻止当前元素后续的绑定事件
			while ( (handleObj=matched.handlers[j++]) && !event.isImmediatePropagationStopped() ){

				if ( !event.rnamespace || event.rnamespace.test(handleObj.namespace) ){

					event.handleObj=handleObj;
					event.data=handleObj.data;

					// 执行$ele.on方法添加的回调函数,this关键字为绑定事件元素,首参为event
					ret=( (jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler )
						.apply(matched.elem,args);

					// $ele.on方法返回false阻止默认事件以及冒泡行为
					if ( ret!==undefined ){
						if ( ( event.result=ret )===false ){
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

		if ( special.postDispatch ){
			special.postDispatch.call(this,event);
		}

		return event.result;
	},

3、$.event.handlers(event,handlers)取出元素data对象中的回调函数:

获取当前元素及其父元素data中的回调函数及回调上下文。

// 获取绑定元素Data对象中存储的回调函数,以及该回调执行的上下文,即期望的或绑定事件的节点元素
	// 返回handlerQueue数组个数由回调函数上下文个数决定,即捕获元素selector个数+1
	// handlers方法内部this关键字为绑定事件元素
	handlers:function(event,handlers){
		var i, matches, sel, handleObj,
			handlerQueue=[],
			delegateCount=handlers.delegateCount,// $ele.on方法设置捕获元素的次数
			cur=event.target;

		// Support: IE <=9
		// Find delegate handlers
		// Black-hole SVG <use> instance trees (#13180)
		//
		// Support: Firefox <=42
		// Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
		if ( delegateCount && cur.nodeType &&
			( event.type!=="click" || isNaN(event.button) || event.button<1 ) ){

			for ( ; cur!==this; cur=cur.parentNode || this ){

				// Don't check non-elements (#13208)
				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
				if ( cur.nodeType===1 && ( cur.disabled!==true || event.type!=="click" ) ){
					matches=[];
					for ( i=0; i<delegateCount; i++ ){
						handleObj=handlers[i];

						sel=handleObj.selector+" ";

						if ( matches[sel]===undefined ){
							matches[sel]=handleObj.needsContext ?
								jQuery(sel,this).index(cur)>-1 :// sel为数组形式,cur为其中一项
								jQuery.find(sel,this,null,[cur]).length;
						}
						if ( matches[sel] ){
							matches.push(handleObj);
						}
					}
					if ( matches.length ){
						handlerQueue.push( {elem:cur,handlers:matches} );
					}
				}
			}
		}

		// $ele.on、$ele.one方法未添加捕获元素,获取绑定的回调函数,以及该回调执行的上下文,即绑定元素
		if ( delegateCount<handlers.length ){
			handlerQueue.push( {elem:this, handlers:handlers.slice(delegateCount)} );
		}

		return handlerQueue;
	},

4、$.event.remove(elem,types,handler,selector,mappedTypes)移除data对象的回调函数:

通过引用类型移除元素data对象中的回调函数,清空完毕后释放内存,并用浏览器原生语句removeEventListener方法解绑回调。

// 删除绑定事件,由回调guid、捕获元素、事件类型查询绑定事件并撤销
	remove:function(elem,types,handler,selector,mappedTypes){

		var j, origCount, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData=dataPriv.hasData(elem) && dataPriv.get(elem);

		if ( !elemData || !( events=elemData.events ) ){
			return;
		}

		types=( types || "" ).match(rnotwhite) || [""];
		t=types.length
		while ( t-- ){
			tmp=rtypenamespace.exec(types[t]) || [];
			type=origType=tmp[1];
			namespaces=( tmp[2] || "" ).split(".").sort();

			// 当type赋值为空,解绑相同命名空间namespaces下所有事件
			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;
			handlers=events[type] || [];
			tmp=tmp[2] && new RegExp("(^|\\.)"+namespaces.join("\\.(?:.*\\.|)")+"(\\.|$)");

			origCount=j=handlers.length;
			while ( j-- ){
				handleObj=handlers[j];// 获取Data对象中的回调函数队列

				if ( ( mappedTypes || origType===handleObj.origType ) &&// 相同事件类型或所有
					( !handler || handler.guid===handleObj.guid ) &&// 相同绑定函数或所有
					( !tmp || tmp.test(handleObj.namespace) ) &&// 相同命名空间或所有
					( !selector || selector===handleObj.selector ||// 相同捕获元素或所有
						selector==="**" && handleObj.selector ) ){
					handlers.splice(j,1);

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

			// special.teardown方法更新文档节点绑定focus、blur事件的数量,没有事件时解绑函数
			if ( origCount && !handlers.length ){
				if ( !special.teardown ||
					special.teardown.call(elem,namespaces,elemData.handle)===false ){

					jQuery.removeEvent(elem,type,elemData.handle);
				}

				delete events[type];
			}
		}

		if ( jQuery.isEmptyObject( events ) ){// 清空Data对象,节省内存
			dataPriv.remove(elem,"handle events");
		}
	},

 5、$.event.trigger(event,data,elem,onlyHandlers) 以jQuery方式触发绑定函数:

 先根据传参event是否jQuery封装的event对象,若不是,通过传参event获取jQuery封装的event对象。该event对象添加isTrigger属性,为真值时使用$.event.trigger触发事件,为否值时由浏览器点击等事件触发。

若当前事件类型的sepcial.trigger函数存在,执行该函数并返回,已知focus/blur事件为此种情况,仅得到或失去焦点。其他情况,先以eventPath数组形式获取存储向上冒泡的元素路径,接着获取data对象中各元素的回调函数并执行,以及元素路径上eventPath[i][ontype]用浏览器原生语句绑定在元素上的回调函数(该回调返回否值,阻止浏览器默认事件触发),接着根据sepcial._default函数返回值为否的情况,触发调用trigger方法元素elem[ontype]函数。

// onlyHandlers阻止向上冒泡
	trigger:function(event,data,elem,onlyHandlers){

		var i, cur, tmp, bubbleType, ontype, handle, special,
			eventPath=[elem || document],
			type=hasOwn.call(event,"type") ? event.type : event,
			namespaces=hasOwn.call(event,"namespace") ? event.namespace.split(".") : [];

		cur=tmp=elem=elem || document;

		if ( elem.nodeType===3 || elem.nodeType===8 ){// 文本节点和注释节点不能触发事件
			return;
		}

		// focus/blur事件触发过程中,focusin/focusout事件不触发
		if ( rfocusMorph.test(type+jQuery.event.triggered) ){
			return;
		}

		if ( type.indexOf(".")>-1 ){
			namespaces=type.split(".");
			type=namespaces.shift();
			namespaces.sort();
		}
		ontype=type.indexOf(":")<0 && "on"+type;

		// 获取或创建jquery封装的event对象
		event=event[jQuery.expando] ? event : new jQuery.Event(type,typeof event==="object" && event);

		// event.isTrigger为真值时,由用户调用$ele.trigger方法触发,否则为浏览器事件自动出阿发
		event.isTrigger=onlyHandlers ? 2 : 3;
		event.namespace=namespaces.join(".");
		// 以"."号分割的字符串中包含namespaces以"."号分割后的各子字符串
		event.rnamespace=event.namespace ? 
			new RegExp("(^|\\.)"+namespaces.join("\\.(?:.*\\.|)")+"(\\.|$)") : null;

		event.result=undefined;
		if ( !event.target ) {
			event.target=elem;
		}

		data=data==null ? [event] : jQuery.makeArray(data,[event]);// 作为参数传入回调函数

		// special.trigger输入框获得focus或失去blur焦点,复选框选中click事件触发,阻止向上冒泡
		special=jQuery.event.special[type] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply(elem,data)===false ){
			return;
		}

		// eventPath添加向上冒泡元素路径
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow(elem) ){

			bubbleType=special.delegateType || type;
			if ( !rfocusMorph.test(bubbleType+type) ){// rfocusMorph函数校验blur、focus返回true
				cur=cur.parentNode;
			}
			for ( ; cur; cur=cur.parentNode ){
				eventPath.push(cur);
				tmp=cur;
			}

			if ( tmp===( elem.ownerDocument || document ) ){
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}

		// 执行回调函数,按条件执行浏览器原生的事件处理函数
		i=0;
		while ( ( cur=eventPath[i++] ) && !event.isPropagationStopped() ){

			event.type=i>1 ? bubbleType : special.bindType || type;

			// 获取绑定回调函数并执行,以cur作为上下文
			handle=( dataPriv.get(cur,"events") || {} )[event.type] && dataPriv.get(cur,"handle");
			if ( handle ){
				handle.apply(cur,data);
			}

			// 获取并执行浏览器原生事件cur[ontype],该事件函数返回否值时,阻止浏览器默认事件执行
			handle=ontype && cur[ontype];
			if ( handle && handle.apply && acceptData(cur) ){
				event.result=handle.apply(cur,data);
				if ( event.result===false ){
					event.preventDefault();
				}
			}
		}
		event.type=type;

		if ( !onlyHandlers && !event.isDefaultPrevented() ){

			// special._default返回为真值时,不执行elem[type]
			// special._default为否则或其返回为否值时,执行elem[type]
			if ( ( !special._default || 
				special._default.apply(eventPath.pop(),data)===false ) && acceptData(elem) ){

				// 获取并执行浏览器原生事件elem[type],执行过程中缓存elem[ontype]
				if ( ontype && jQuery.isFunction(elem[type]) && !jQuery.isWindow(elem) ){

					tmp=elem[ontype];

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

					jQuery.event.triggered=type;
					elem[type]();
					jQuery.event.triggered=undefined;

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

		return event.result;
	},
  

 5、jQuery封装的event对象,构造函数为$.Event:

继承原生event对象的属性如offsetX等,按键which属性通过改写后获得。

添加preventDefault方法用于阻止默认事件触发,isDefaultPrevented用于检测默认事件是否不予触发。

stopPropagation方法用于阻止事件冒泡,isPropagationStopped用于检测向上冒泡是否已阻止。

stopImmediatePropagation方法用于阻止同一元素的后续事件触发,isImmediatePropagationStopped检测是否阻止。

// 调用e.preventDefault方法时,改变状态返回函数e.isDefaultPrevented的输出值
// 从而对回调函数产生影响,stopPropagation、stopImmediatePropagation方法相同
jQuery.Event.prototype={
	constructor:jQuery.Event,
	isDefaultPrevented:returnFalse,
	isPropagationStopped:returnFalse,
	isImmediatePropagationStopped:returnFalse,
	isSimulated:false

	preventDefault:function(){// 阻止默认事件
		var e=this.originalEvent;

		this.isDefaultPrevented=returnTrue;

		if ( e && !this.isSimulated ){
			e.preventDefault();// 调用浏览器原生语句阻止默认事件
		}
	},
	stopPropagation:function(){// 阻止向上冒泡
		var e=this.originalEvent;

		this.isPropagationStopped=returnTrue;

		if ( e && !this.isSimulated ){
			e.stopPropagation();
		}
	},
	stopImmediatePropagation:function(){// 阻止后续同类事件执行
		var e=this.originalEvent;

		this.isImmediatePropagationStopped=returnTrue;

		if ( e && !this.isSimulated ){
			e.stopImmediatePropagation();
		}

		this.stopPropagation();
	}
};

// 回调函数参数event属性设置取值、赋值,模块内立即执行
jQuery.each({
	altKey: true,
	bubbles: true,
	cancelable: true,
	changedTouches: true,
	ctrlKey: true,
	detail: true,
	eventPhase: true,
	metaKey: true,
	pageX: true,
	pageY: true,
	shiftKey: true,
	view: true,
	"char": true,
	charCode: true,
	key: true,
	keyCode: true,
	button: true,
	buttons: true,
	clientX: true,
	clientY: true,
	offsetX: true,
	offsetY: true,
	pointerId: true,
	pointerType: true,
	screenX: true,
	screenY: true,
	targetTouches: true,
	toElement: true,
	touches: true,

	which: function( event ) {
		var button = event.button;

		// Add which for key events
		if ( event.which==null && rkeyEvent.test(event.type) ){
			return event.charCode!=null ? event.charCode : event.keyCode;
		}

		// Add which for click: 1 === left; 2 === middle; 3 === right
		if ( !event.which && button!==undefined && rmouseEvent.test(event.type)  {
			return ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
		}

		return event.which;
	}
},jQuery.event.addProp);
 

6、$.event.sepcial特殊事件处理:

special:{
		/**
		 * sepcial[type].trigger: $ele.trigger触发事件时使用,避过注册事件的执行和冒泡
		 *        只执行sepcial[type].trigger方法,包含得到失去焦点、复选框点选,focus、blur、click
		 * sepcial[type].setup: 执行$ele.on方法时调用,支持事件类型为focus、blur
		 * 		  将jQuery.event.simulate方法作为回调绑定在文档节点上,
		 * 		  通过simulate触发jQuery.event.trigger方法,进而触发special.trigger执行
		 * 		  event.target单纯获得、失去焦点,情形类同没有冒泡效果的事件
		 * sepcial[type].teardown: 执行$ele.remove方法时调用,支持事件类型为focus、blur
		 * 		  文档节点缓存focus、blur事件数减1,文档节点解绑jQuery.event.trigger方法
		 * sepcial[type]._default: $ele.trigger触发事件时调用,返回为真,阻止ele[event.type]执行
		 * 		  当ele为a标签时,不触发ele["click"]函数跳链接
		 * special[type].preDispatch:绑定函数执行完成前调用
		 * special[type].postDispatch:绑定函数执行完成后调用
		 * special[type].remove: 移除回调函数时执行
		 * special[type]._default:返回真值阻止默认事件,已知a标签点击时不跳连接
		 */
		load:{
			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},
		focus:{
			// $.trigger("focus")阻止事件向上冒泡,只实现元素获得焦点
			trigger:function(){
				if ( this!==safeActiveElement() && this.focus ){
					this.focus();
					return false;
				}
			},
			// focus类型注册的事件添加到elemData.events.focusin对象中,触发focus事件时也不取出
			delegateType:"focusin"
		},
		blur: {
			// $.trigger("focus")阻止事件向上冒泡,只实现元素失去焦点
			trigger:function(){
				if ( this===safeActiveElement() && this.blur ){
					this.blur();
					return false;
				}
			},
			delegateType:"focusout"
		},
		click:{
			// $.trigger("click")阻止事件向上冒泡,只实现复选框元素点中
			trigger:function(){
				if ( this.type==="checkbox" && this.click && jQuery.nodeName(this,"input") ){
					this.click();
					return false;
				}
			},
			// $ele.trigger方法触发时间,ele为a标签时返回为真,阻止ele["click"]执行,不触发跳链接动作
			_default:function(event){
				return jQuery.nodeName(event.target,"a");
			}
		},
		beforeunload:{// 离开页面事件,绑定函数返回值作为页面alert弹窗提示,原理不明?
			postDispatch:function(event){
				if ( event.result!==undefined && event.originalEvent ){
					event.originalEvent.returnValue=event.result;
				}
			}
		}
	}
 

相关推荐