jQuery源码学习(版本1.11)-事件处理-工具函数jQuery.event

帅令雪
2023-12-01

概述

本文详细分析jquery-1.11.1.js源码文件行数:4269~4906;
代码简介:定义了jQuery.event命名空间,存放了事件处理底层的工具函数add-增加事件监听,remove-移除事件监听,trigger-触发事件,dispatch-事件分发等,JQ对象利用JQ原型方法添加事件时,最终都会调用到jQuer.event里的工具方法。
下文进行详细代码分析。

jQuery.event

// 事件管理的工具函数
jQuery.event = {

	global: {},

	// 添加绑定事件
	add: function( elem, types, handler, data, selector ) {
		var tmp, events, t, handleObjIn,
			special, eventHandle, handleObj,
			handlers, type, namespaces, origType,
			// 获取elem在jQuery.cache中对应的缓存(如果没定义_data会自动初始化一个新的)
			elemData = jQuery._data( elem );

		// elemData即缓存不存在,说明elem不适合绑定事件,直接返回
		if ( !elemData ) {
			return;
		}

		// 处理handler是一个object而不单纯是funciton的场景
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

		// 为handler增加一个独有id,用于remove判断解除绑定的是不是同一个回调(我估计是直接用===比较引用会损耗性能)
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// 初始化缓存里的events对象,后面用于保存事件类型和对应回调
		if ( !(events = elemData.events) ) {
			events = elemData.events = {};
		}
		// 初始化elemData.handle,统一执行所有回调的函数
		if ( !(eventHandle = elemData.handle) ) {
			eventHandle = elemData.handle = function( e ) {
				// 分发处理事件,trigger方法中会使用原生监听方法将eventHandle作为原生方法的回调
				return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ?
					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
					undefined;
			};
			// 防止IE内存泄露
			eventHandle.elem = elem;
		}

		// 本方法支持事件类型用空格隔开传入,处理type保存了多种事件类型的场景,将其分割开
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			// rtypenamespace这个正则是用来匹配"."前后片段的,最终过滤得到type是需要的事件类型
			// 具体考虑场景暂时还不确定,估计是用来兼容一些传入了命名空间的场景
			// 命名空间是提供一种根据不同命名空间来处理事件的机制
			tmp = rtypenamespace.exec( types[t] ) || [];
			// 由代码可推测tmp[1]是原始类型
			type = origType = tmp[1];
			// tmp[2]是命名空间,使用split分割成数组
			namespaces = ( tmp[2] || "" ).split( "." ).sort();

			//最终得到需要的type,不存在的话就循环下一个
			if ( !type ) {
				continue;
			}

			// 获取其special里保存的事件类型对应的对象
			// 后面代码可知,load,focus,blur,click,beforeunload五种事件类型均有对应的特殊对象
			// 不存在则使用空对象
			special = jQuery.event.special[ type ] || {};

			// 如果selector存在,则使用special.delegateType作为type,否则使用special.bindType 
			// special.delegateType和special.bindType都为空(前面special可能只是空对象),则不改变type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// 上一行代码type可能改变了,因此更新一下对应的special对象
			special = jQuery.event.special[ type ] || {};

			// 构造一个handleObj
			// 这里使用extend方法将handleObjIn扩展进handleObj,假如handleObjIn为空,就会直接返回handleObj
			// extend内部确定需要扩展的target时,使用的是形参长度,因此只要用来扩展的对象传了形参,即使为空,也不会变为将handleObj扩展进jQuery
			// 只当传了一个参数时,target才会变为this,即jQuery本身
			handleObj = jQuery.extend({
				type: type,
				origType: origType,
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
				// 保存命名空间,网上查找用法说是可让用户根据命名空间,对事件进行不同的处理
				namespace: namespaces.join(".")
			}, handleObjIn );

			// 初始化该事件类型的缓存
			if ( !(handlers = events[ type ]) ) {
				// 初始化缓存数组handlers,用于保存回调
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// setup暂时不理解是什么东西
				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
					// 调用elem.addEventListener或elem.attachEvent监听事件,回调使用eventHandle
					// eventHandle里面调用jQuery.event.dispatch,会对缓存里面的对应事件类型回调一一执行
					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 );

				// 感觉这是多余的分支,从前面代码分析,handleObj.handler跟handler永远是相同的引用
				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// 将回调handleObj添加进handlers
			if ( selector ) {
				// 这里使用splice时插入对象(handlers.delegateCount是位置,第二个参数是0表明不删除,第三个是需要插入数组的对象,会被放在handlers.delegateCount前面)
				// 由于是插入的,因此事件分发执行的时候也会被优先执行
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// 记录曾经使用过的事件类型
			jQuery.event.global[ type ] = true;
		}

		// 规避ie的内存泄露问题
		elem = null;
	},

	// 解除绑定事件
	remove: function( elem, types, handler, selector, mappedTypes ) {
		var j, handleObj, tmp,
			origCount, t, events,
			special, handlers, type,
			namespaces, origType,
			// 判断后获取elem缓存
			elemData = jQuery.hasData( elem ) && jQuery._data( elem );

		// elemData不存在或elemData.events则返回,这里同时也声明了引用events = elemData.events
		if ( !elemData || !(events = elemData.events) ) {
			return;
		}

		// 兼容空格分开多种事件类型的场景
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		// 循环遍历需要移除的事件类型
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[t] ) || [];
			// 获取原始类型,让origType引用上,后续会用到origType
			type = origType = tmp[1];
			namespaces = ( tmp[2] || "" ).split( "." ).sort();

			// 如果type为空,说明types传入的也是一个空值,则需要解除绑定所有的事件监听
			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("\\.(?:.*\\.|)") + "(\\.|$)" );

			// Remove matching events
			origCount = j = handlers.length;
			// 循环遍历,找到需要移除的回调
			while ( j-- ) {
				handleObj = handlers[ j ];

				// 要同时满足多个表达式,才进入分支移除回调
				if ( ( mappedTypes || origType === handleObj.origType ) &&
					( !handler || handler.guid === handleObj.guid ) &&
					( !tmp || tmp.test( handleObj.namespace ) ) &&
					( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
					// 调用splice移除对应的回调
					handlers.splice( j, 1 );

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

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {
				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

		// Remove the expando if it's no longer used
		if ( jQuery.isEmptyObject( 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" );
		}
	},

	// 主动触发事件函数
	trigger: function( event, data, elem, onlyHandlers ) {
		var handle, ontype, cur,
			bubbleType, special, tmp, i,
			// 定义需要触发事件的元素队列,后续用于保存父节点,制造冒泡效果,让父节点也触发事件
			eventPath = [ elem || document ],
			// 确定事件类型
			type = hasOwn.call( event, "type" ) ? event.type : event,
			// 确定命名空间
			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];

		// 定义当前cur,tmp,确定当前触发事件的元素
		cur = tmp = elem = elem || document;

		// 不处理text跟comment节点的场景
		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}

		// 不触发focus/blur focusin/out事件?
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}

		// 校验type是否有命名空间
		if ( type.indexOf(".") >= 0 ) {
			// 分割type
			namespaces = type.split(".");
			// 获取第一个作为原始类型
			type = namespaces.shift();
			// 其他的全部作为事件类型的命名空间,命名空间的作用是可让使用者根据不同的情况处理事件
			namespaces.sort();
		}
		// 获取原生事件类型,例如onclick,后面用于查找elem内是否定义了原生事件的回调
		ontype = type.indexOf(":") < 0 && "on" + type;

		// 如果event不存在jQuery.expando属性,则使用jQuery.Event重新创建一个
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );

		// 以下几行增加了event的一些属性
		// 根据onlyHandlers确定event.isTrigger,值是2或3,用处暂时不明
		event.isTrigger = onlyHandlers ? 2 : 3;
		event.namespace = namespaces.join(".");
		event.namespace_re = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
			null;

		// 清除一下event.result,因为有可能之前触发过
		event.result = undefined;
		// 确定event.target
		if ( !event.target ) {
			event.target = elem;
		}

		// 将data数据克隆进新数组中返回(前提是data是类数组对象,否则makeArray会直接将data加进新数组,这样就是引用了原data对象而不是克隆)
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );

		// 事件类型的特殊处理方法,如果满足条件,也需要触发
		special = jQuery.event.special[ type ] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}

		// onlyHandlers的应该是决定是否触发事件的默认行为,如冒泡
		// onlyHandlers变量字面意思,true的时候只触发该事件保存的handlers,而不触发默认行为,为false的时候才能够触发默认行为
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			// 遍历当前事件节点的父节点
			for ( ; cur; cur = cur.parentNode ) {
				// 塞进需要触发事件的元素队列
				eventPath.push( cur );
				// 重置tmp
				tmp = cur;
			}

			// 最后增加一个tmp.defaultView || tmp.parentWindow || window ,必须确定tmp是elem的ownerDocument或当前document这个变量的引用对象
			if ( tmp === (elem.ownerDocument || document) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}

		// 冒泡事件,要满足event.isPropagationStopped()为false的条件(这里怀疑存在问题,原始节点的事件触发应该是不需要判断isPropagationStopped的)
		i = 0;
		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {

			// 第一个是必须触发的原始事件,因此这里判断一下,当i > 1后,就是冒泡事件了
			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;

			// 从之前分析的代码可知,events是一个对象,属性是事件类型,值是保存了回调的数组
			// 以下代码是先判断回调数组是否存在,然后再直接获取jQuery._data( cur, "handle" ),前面代码可知这是统一执行所有回调的函数
			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
			// 执行事件回调
			if ( handle ) {
				handle.apply( cur, data );
			}

			// 从节点中获取原生事件回调
			handle = ontype && cur[ ontype ];
			// 执行原生事件回调
			if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
				// 定义事件执行结果
				event.result = handle.apply( cur, data );
				// 返回false的话要停止事件默认行为(这个是DOM标准吗?还是jquery自己增加的功能)
				if ( event.result === false ) {
					event.preventDefault();
				}
			}
		}
		event.type = type;

		// 触发事件的默认行为
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {

			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
				jQuery.acceptData( elem ) ) {

				// 先判断ontype,elem[ type ]是否存在,而elem不能为window,才能触发默认行为
				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {

					// 引用原生事件回调
					tmp = elem[ ontype ];

					// 然后清空elem[ ontype ],防止触发默认行为的时候再度触发事件
					if ( tmp ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;
					try {
						// 触发事件默认行为(原来去掉on之后在节点里的这个属性就是事件的默认行为函数,第一次了解到这个知识)
						elem[ type ]();
					} catch ( e ) {
						// 捕获IE<9的异常,以免影响脚本运行
					}
					jQuery.event.triggered = undefined;

					// 触发完默认行为后再重置原生事件elem[ ontype ]引用
					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		// 返回执行结果
		return event.result;
	},

	// 事件分发
	dispatch: function( event ) {

		// 调用fix将传入事件修复
		event = jQuery.event.fix( event );

		var i, ret, handleObj, matched, j,
			handlerQueue = [],
			args = slice.call( arguments ),
			// 从缓存中获取elem对应的回调数组(dispatch方法没有elem的形参,但是从前面的add方法里可以看出,使用apply去保证this指向elem)
			handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
			// 获取对应事件类型的special对象
			special = jQuery.event.special[ event.type ] || {};

		// 改变入参指向,使用fix之后的event
		args[0] = event;
		event.delegateTarget = this;

		// 暂时不理解preDispatch的具体使用场景
		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
			return;
		}

		// jQuery.event.handlers返回的是一个回调数组,里面保存的是对象,每个对象都存在属性elem,以及handlers(这个是数组,里面保存handleObj,与events[type]类似)
		handlerQueue = jQuery.event.handlers.call( this, event, handlers );

		// 首先处理的是delegateCount的回调对象
		i = 0;
		while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
			event.currentTarget = matched.elem;

			j = 0;
			// 遍历matched.handlers,定义局部变量handleObj,后续使用
			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
					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
							.apply( matched.elem, args );

					if ( ret !== undefined ) {
						// 返回结果为false,阻止事件默认行为,并且停止冒泡
						if ( (event.result = ret) === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

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

		// 返回事件结果,从前面的逻辑可以看出,每一个回调都可能会有result
		// 这样的话event.result实际取的就是最后一个有result的回调的result
		return event.result;
	},

	// 生成一个数组handlerQueue,用于分发事件(入参两个,event是jQuery.Event对象,handlers是回调对象缓存数组events[type])
	handlers: function( event, handlers ) {
		var sel, handleObj, matches, i,
			handlerQueue = [],
			// 获取delegateCount
			// 从前面代码分析,当缓存数组events[type]里的回调对象handlerObj,存在selector的时候,就会被插入到数组前面
			// 因此这里获取到的delegateCount表明的应该是需要特殊处理的回调对象的个数
			delegateCount = handlers.delegateCount,
			// 获取当前事件作用的节点
			cur = event.target;

		// 满足条件的进入分支,
		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {

			// 从cur开始,然后第二轮是遍历获取cur.parentNode(要进入这个分支,首先第一遍cur就不能为this)
			for ( ; cur != this; cur = cur.parentNode || this ) {
				// 必须是element节点
				if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) {
					matches = [];
					// 遍历delegateCount
					for ( i = 0; i < delegateCount; i++ ) {
						handleObj = handlers[ i ];

						// 获取handleObj.selector,后面加空格是为了避免与属性冲突
						sel = handleObj.selector + " ";

						// 下面分支使用Sizzle找到selector对应元素的个数,存进matches[ sel ]
						// 从这里可以猜测jQuery.event的delegateCount这个功能,应该是为了对特殊选择的某些元素执行回调而存在的
						// 由于外面使用了双重循环,这样会导致matches.push( handleObj )多次执行,matches存在多个重复handleObj
						if ( matches[ sel ] === undefined ) {
							matches[ sel ] = handleObj.needsContext ?
								jQuery( sel, this ).index( cur ) >= 0 :
								jQuery.find( sel, this, null, [ cur ] ).length;
						}
						// 个数大于0,则把handleObj放进数组
						if ( matches[ sel ] ) {
							matches.push( handleObj );
						}
					}
					// 数组长度大于0,说明存放了handleObj,这些存放的handleObj的selector找到的元素个数不为0,塞进handlerQueue
					if ( matches.length ) {
						handlerQueue.push({ elem: cur, handlers: matches });
					}
				}
			}
		}

		// 将剩余的handleObj截取出新数组,放进对象的handlers属性然后push进handlerQueue
		if ( delegateCount < handlers.length ) {
			handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
		}

		// 返回数组
		return handlerQueue;
	},

	fix: function( event ) {
		// 使用JQ版本标志判断event是否jQuery.Event对象,是则直接返回
		if ( event[ jQuery.expando ] ) {
			return event;
		}

		// Create a writable copy of the event object and normalize some properties
		var i, prop, copy,
			// 事件类型
			type = event.type,
			// 后面代码event引用会改变为新事件对象,这里先引用旧事件对象
			originalEvent = event,
			// 对应勾子对象
			fixHook = this.fixHooks[ type ];

		// 若对应事件类型的勾子不存在,则重新定义
		// 判断其是否是特殊事件类型,是则使用其勾子,否则定义为空对象
		if ( !fixHook ) {
			this.fixHooks[ type ] = fixHook =
				rmouseEvent.test( type ) ? this.mouseHooks :
				rkeyEvent.test( type ) ? this.keyHooks :
				{};
		}
		// 引用勾子对象的属性props
		// this.props则是jQuery.event.props里的元素,使用concat构造新数组,表明这些也是构建事件对象需要的属性
		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

		// 建立一个新事件对象
		event = new jQuery.Event( originalEvent );

		// 循环将属性拷贝进新事件对象
		i = copy.length;
		while ( i-- ) {
			prop = copy[ i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// 兼容IE<9,IE不存在target,但存在srcElement,为了后面方便使用,这里统一兼容成target
		if ( !event.target ) {
			event.target = originalEvent.srcElement || document;
		}

		// 兼容Chrome 23+, Safari存在的target为text类型的场景,改变为其父对象,不能使用text类型
		if ( event.target.nodeType === 3 ) {
			event.target = event.target.parentNode;
		}

		// 兼容IE<9,metaKey会出现为undefined的场景,将其修正为boolean类型,undefined为变为false
		event.metaKey = !!event.metaKey;

		// 若勾子存在过滤器,则返回过滤后的事件对象,否则直接返回新事件对象
		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
	},

	// 包含一些事件常用属性,让KeyEvent和MouseEvent共享
	// 新建JQ事件对象的时候,这些属性就会被扩展进去
	props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),

	// fix的勾子,应该是提供自定义勾子使用的,如果不存在的话,fix方法里面会使用keyHooks跟mouseHooks
	fixHooks: {},

	// 键盘事件勾子
	keyHooks: {
		// 勾子对象里的props,在fix方法里会被添加进新事件
		props: "char charCode key keyCode".split(" "),
		// filter也会在fix里被调用
		filter: function( event, original ) {

			// 添加一个which属性
			if ( event.which == null ) {
				// 使用原生事件的charCode或者keyCode
				event.which = original.charCode != null ? original.charCode : original.keyCode;
			}

			return event;
		}
	},

	// 鼠标事件勾子
	mouseHooks: {
		// 勾子对象里的props,在fix方法里会被添加进新事件
		props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
		filter: function( event, original ) {
			var body, eventDoc, doc,
				button = original.button,
				fromElement = original.fromElement;

			// 计算pageX/Y
			if ( event.pageX == null && original.clientX != null ) {
				eventDoc = event.target.ownerDocument || document;
				doc = eventDoc.documentElement;
				body = eventDoc.body;

				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
			}

			if ( !event.relatedTarget && fromElement ) {
				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
			}

			if ( !event.which && button !== undefined ) {
				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
			}

			return event;
		}
	},

	// 特殊事件类型的一些属性跟方法,这些属性跟方法均在前面的jQuery.event中被使用到,应该可以看做是跟勾子类似的对象
	special: {
		load: {
			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},
		focus: {
			// Fire native event if possible so blur/focus sequence is correct
			trigger: function() {
				if ( this !== safeActiveElement() && 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
					}
				}
			},
			delegateType: "focusin"
		},
		blur: {
			trigger: function() {
				if ( this === safeActiveElement() && this.blur ) {
					this.blur();
					return false;
				}
			},
			delegateType: "focusout"
		},
		click: {
			// For checkbox, fire native event so checked state will be right
			trigger: function() {
				if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
					this.click();
					return false;
				}
			},

			// For cross-browser consistency, don't fire native .click() on links
			_default: function( event ) {
				return jQuery.nodeName( event.target, "a" );
			}
		},

		beforeunload: {
			postDispatch: function( event ) {

				// Support: Firefox 20+
				// Firefox doesn't alert if the returnValue field is not set.
				if ( event.result !== undefined && event.originalEvent ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
	},

	// simulate这个方法暂时也是不理解其用途
	// 貌似是模仿一些具体类型的事件,然后触发
	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.remove里会用到
jQuery.removeEvent = document.removeEventListener ?
	// 判断document.removeEventListener,存在说明支持则使用如下方法
	function( elem, type, handle ) {
		if ( elem.removeEventListener ) {
			elem.removeEventListener( type, handle, false );
		}
	} :
	// 不支持则使用IE的detachEvent方法
	function( elem, type, handle ) {
		var name = "on" + type;

		if ( elem.detachEvent ) {

			// #8545, #7054以防自定义事件的内存泄露IE6-8
			// IE解除事件绑定,需要节点上存在该事件属性
			if ( typeof elem[ name ] === strundefined ) {
				elem[ name ] = null;
			}

			elem.detachEvent( name, handle );
		}
	};


 类似资料: