本片随笔主要是分析了下jQuery的事件模型,即如何统一事件对象,以及处理过程。
这里简要说明一下几点:
jQuery通过统一的方法(第62行),eventHandle函数进行事件的分发,利用jQuery.Event统一修正了浏览器的event对象,注册事件处理器时,也是注册eventHandle,
然后统一将相关的事件信息,存储在与dom相联系的jQuery.cache缓存中;
值得注意的还有事件代理和trigger的实现:
(1)事件代理是个很不错的想法,利用了事件冒泡,在父元素上绑定事件,jQuery通过selector与事件处理器handle的selector进行匹配,从而达到代理的作用,
另外,一个元素绑定的事件处理器分为两种:自身绑定的事件处理器 和 子元素绑定的代理事件处理器,显然为了体现冒泡(子元素可能阻止冒泡),当该元素上的某个事件触发时
它的代理事件处理器是要先执行,之后再执行自身的事件处理器。对于live事件而言就是事件代理,与delegate不同的是,它默认是绑定在document上了(如果没有指定context的话)。
(2)trigger主动触发事件,jQuery在trigger上同样很好的实现了事件冒泡,还有元素默认操作
最后:本文主要是介绍和分析原理,对代码主干进行源码解析,对于一些IE兼容性的处理,没有做过多分析,下面是源码分析(中英文注释):
var rformElems = /^(?:textarea|input|select)$/i, rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/, rhoverHack = /(?:^|\s)hover(\.\S+|)\b/, rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|contextmenu)|click/, rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, hoverHack = function( events ) { return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); }; /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. */ jQuery.event = { add: function( elem, types, handler, data, selector ) { var elemData, eventHandle, events, t, tns, type, namespaces, handleObj, handleObjIn, handlers, special; // Don't attach events to noData or text/comment nodes (allow plain objects tho) // 以下条件满足一个则退出事件绑定: // 是文本节点和注释节点 // types为空 // hander为空 // 内部缓存数据(pvt为true) if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { return; } // Caller can pass in an object of custom data in lieu of the handler // 调用这可以传递一个自定义数据来代替handler(事件处理器) // 自定义数据类似: // { // handler : fn // ,selector : '...' // } if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Make sure that the handler has a unique ID, used to find/remove it later // 如果改事件处理器没有guid(没有被添加过),那么给它添加guid,便于之后的查找和删除 // jQuery.guid从1开始计算 if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first // 初始化内部数据events events = elemData.events; if ( !events ) { elemData.events = events = {}; } // 初始化内部数据handle,handle将成为所有事件触发时的处理函数 eventHandle = elemData.handle; if ( !eventHandle ) { 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) ? 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 // 将elem作为eventHandle的属性存储,用来避免IE中非本地事件的内存泄漏 eventHandle.elem = elem; } // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); // hoverHack(...)将hover事件解释成mouseenter和mouseleave两个事件 // 去除字符串前后的的空格,并拆分成事件类型数组 types = jQuery.trim( hoverHack(types) ).split( " " ); for ( t = 0; t < types.length; t++ ) { tns = rtypenamespace.exec( types[t] ) || []; // 事件类型 type = tns[1]; // 命名空间(值可能为undefined) // 多级命名空间(其实没所谓的多级),排序 namespaces = ( tns[2] || "" ).split( "." ).sort(); // If event changes its type, use the special event handlers for the changed type // 特殊的事件需要进行特殊处理,比如focus,blur等 special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type // 如果selector有值,则表示适用代理 type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers // 每一个事件处理器都对应存储着一个handleObj // type与oriType肯能不同,比如oriType为mouseenter,type为mouseover // 这里extend了handleObjIn,这意味着使用者可以在传递进来的handle里大做文章,存储自定义的数据。 handleObj = jQuery.extend({ type: type, // fix后的事件类型 origType: tns[1], // 原始的事件类型 data: data, // 传递进来的数据 handler: handler, // 事件处理器 guid: handler.guid, // 事件处理器的唯一id selector: selector, // 代理事件处理器需要的选择器,用来过滤 namespace: namespaces.join(".") //命名空间(经过排序) }, handleObjIn ); // Init the event handler queue if we're the first // 获取type事件下的所有handler(array) handlers = events[ type ]; // 如果为空,进行第一次初始化 if ( !handlers ) { handlers = events[ type ] = []; // handlers中作为代理的个数 handlers.delegateCount = 0; // Only use addEventListener/attachEvent if the special events handler returns false // 特殊事件需要用setup进行特殊处理,如:focusin,focusout等(后面有处理) if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element // 将事件统一绑定到eventHandle,统一接口。 // 采用冒泡,与ie兼容 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 ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front if ( selector ) { // 将事件代理处理器添加到handlers的最前面,方便之后代理处理器优先执行,用的很巧妙 handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { // 普通事件处理器添加到依次push到handler list中 handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization // 记录哪些事件曾将使用过,为了事件的优化 jQuery.event.global[ type ] = true; } // Nullify elem to prevent memory leaks in IE // 去除elem的引用,防止ie内存泄露 elem = null; }, global: {}, // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { var t, tns, type, origType, namespaces, origCount, j, events, special, eventType, handleObj, elemData = jQuery.hasData( elem ) && jQuery._data( elem ); if ( !elemData || !(events = elemData.events) ) { return; } // Once for each type.namespace in types; type may be omitted types = jQuery.trim( hoverHack( types || "" ) ).split(" "); for ( t = 0; t < types.length; t++ ) { tns = rtypenamespace.exec( types[t] ) || []; type = origType = tns[1]; namespaces = tns[2]; // Unbind all events (on this namespace, if provided) for the element // 如果types为'.mynamespace.hello'时,type此时就为空,那么删除该命名空间下的所有事件 if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; // 修正type type = ( selector? special.delegateType : special.bindType ) || type; // 该type(事件类型)下的所有函数处理器handleObj(array) eventType = events[ type ] || []; origCount = eventType.length; // 动态生成正则表达式,匹配命名空间 namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null; // Remove matching events // 遍历handleObj list查找匹配的handler,并执行删除操作 // mappedTypes 暂时不知道何用,应该是内部控制吧 for ( j = 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; // 一系列的逻辑,表示的基本意思如下: // 1. origType === handleObj.origTypes是为了处理jQuery会将mouseenter修正为mouseover这种情况,因为此时mouseenter和mouseover的handleObjs // 会处于同一个集合(对象)里,而它的区别就在于handleObj.oriType,这里做判断,保证能够正确删除mouseenter或者mouseover事件下的事件处理器 // 2. 如果handler不存在,那么就当作是删除该事件下的所有函数处理器,如果存在,则要handler.guid === handleObj.guid保证删除指定事件 // 处理器,因为handler和handleObj是通过guid对应的(一对多的关系,即一个事件处理器可以被同一个事件注册和调用多次)。 // 3. 如果命名空间不存在,那么就忽略命名空间,否则需要进行匹配,这里如果命名空间为多级,只需要其中的一级或多级组合便可以指定其,并删除。 // 如:如果注册click事件,命名空间是'aa.bb.cc',处理器为fn1,那么'click.aa','click.aa.cc'都是可以用来指定并删除fn1的(所以说是多级是不合理的)。 // 4. 如果selector不存在,那么忽略selector,否则如果存在则寻找selector === handleObj.selector的事件处理器进行删除,存在一个特殊值'**', // 如果selector为'**',那么删除所有存在selector的handleObj if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !namespaces || namespaces.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { //这里的设计比较好,保整handleObj的正常删除 eventType.splice( j--, 1 ); if ( handleObj.selector ) { // 事件代理处理器个数减减 eventType.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) // eventType如果从有到无,那么进行一系列的清除工作,special事件仍然做自己的特殊处理 if ( eventType.length === 0 && origCount !== eventType.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } // 删除缓存中events[type] delete events[ type ]; } } // Remove the expando if it's no longer used // 如果不存在任何事件处理器,则去除elemData.handle(所有事件的统一事件处理器) 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删除events,是为了做好清理工作(包括dom上的expando属性或者普通js对象的expando对象,以及缓存在jQuery.cahe的数据) jQuery.removeData( elem, "events", true ); } }, // Events that are safe to short-circuit if no handlers are attached. // Native DOM events should not be added, they may have inline handlers. customEvent: { "getData": true, "setData": true, "changeData": true }, // onlyHandlers在调用triggerHandler时使用 trigger: function( event, data, elem, onlyHandlers ) { // 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, // 兼容jQuery.Event(object) 和 event(string) type = event.type || event, namespaces = []; // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { 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(); } if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { // No jQuery handlers for this event type, and it can't have inline handlers return; } // Caller can pass in an Event, Object, or just an event type string event = typeof event === "object" ? // jQuery.Event object // jQuery.Event或者修正过的event对象 event[ jQuery.expando ] ? event : // Object literal // 字面对象,如{type:'click',...} new jQuery.Event( type, event ) : // Just the event type (string) // event(string),如:'click',则创建jQuery.Event对象 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 : ""; // Handle a global trigger if ( !elem ) { // TODO: Stop taunting the data cache; remove global events and always attach to document cache = jQuery.cache; for ( i in cache ) { if ( cache[ i ].events && cache[ i ].events[ type ] ) { jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); } } return; } // Clean up the event in case it is being reused // 清理event,防止该event正在使用中,有残留历史 event.result = undefined; if ( !event.target ) { event.target = elem; } // Clone any incoming data and prepend the event, creating the handler arg list // 将data转换为数组 data = data != null ? jQuery.makeArray( data ) : []; // 将事件对象插入数组最前面(开始位置),最好将这个数组作为参数传递给事件处理函数 data.unshift( event ); // Allow special events to draw outside the lines special = jQuery.event.special[ type ] || {}; if ( special.trigger && special.trigger.apply( elem, data ) === 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, 'click'], [elemParent, 'click'],...,[window,'click']] eventPath = [[ elem, special.bindType || type ]]; if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { bubbleType = special.delegateType || type; cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; for ( old = elem; cur; cur = cur.parentNode ) { eventPath.push([ cur, bubbleType ]); old = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) // 冒泡是一直冒泡到window对象 if ( old === (elem.ownerDocument || document) ) { eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); } } // Fire handlers on the event path // 依次冒泡调用指定type的事件吃利器 for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { cur = eventPath[i][0]; event.type = eventPath[i][1]; // 查询每个元素type类型的事件处理器 handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { // 这里其实很重要,在进行冒泡的时候,传递的是同一个event对象(在data中) // 也就是在这里便可以通过event对象或者return false,随时停止冒泡 handle.apply( cur, data ); } // Note that this is a bare JS function and not a jQuery handler // 处理通过onType属性添加的事件处理器(如:elem.onClick = function(){...};) // 这里就可以知道trigger的时候onType事件是在通过jquery添加的事件后面执行的 handle = ontype && cur[ ontype ]; if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { // 注意这里onType事件处理器的return false只有可能阻止默认行为 event.preventDefault(); } } // 修正type,防止事件处理器改变event.type的值 event.type = type; // If nobody prevented the default action, do it now // 这里主要是用来执行元素的默认操作 if ( !onlyHandlers && !event.isDefaultPrevented() ) { 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 ) ) { // Don't re-trigger an onFOO event when we call its FOO() method // 假设type为click // 因为下面想通过click()来触发默认操作,但是又不想执行对应的事件处理器(re-trigger), // 所以需要做两方面工作: // 首先将elem.onclick = null; // 然后将jQuery.event.triggered = 'click'; 将在入口handle(第62行)不再dispatch了 // 之后再将它们还原 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.click(),(即trigge click)再次冒泡,不过这次执行入口handle,不会执行dispatch之后的代码了,只为触发默认操作 elem[ type ](); jQuery.event.triggered = undefined; if ( old ) { elem[ ontype ] = old; } } } } return event.result; }, dispatch: function( event ) { // Make a writable jQuery.Event from the native event object // event || window.event 兼容标准事件模型和IE事件模型 // fix 修正event,返回jQuery.Event对象 event = jQuery.event.fix( event || window.event ); var i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related, // handle数组 handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), // handle中代理的个数 delegateCount = handlers.delegateCount, // 浏览器传来的参数,伪数组转换为真正的数组 // args[0]为原生的event对象 args = [].slice.call( arguments ), run_all = !event.exclusive && !event.namespace, special = jQuery.event.special[ event.type ] || {}, handlerQueue = []; // Use the fix-ed jQuery.Event rather than the (read-only) native event // 修正args[0]。 // 因为在IE下,传入的参数event为undefined,就意味着arguments.length为0,改变event并不会影响arguments,所以arguments仍然为undefined args[0] = event; // 事件代理元素 event.delegateTarget = this; // Call the preDispatch hook for the mapped type, and let it bail if desired if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // Determine handlers that should run if there are delegated events // 对于代理处理器,需要进行一定过滤,决定哪些代理处理器可以运行 // Avoid non-left-click bubbling in Firefox (#3861) // 火狐浏览器右键或者中键点击时,会错误地冒泡到document的click事件,并且stopPropagation也无效 // 故这样的代理处理器需要避免 // 如果存在代理处理器(若是click事件,要求是左键触发的),那么才进行事件代理 if ( delegateCount && !(event.button && event.type === "click") ) { // Pregenerate a single jQuery object for reuse with .is() // 生成一个jQuery对象,这里的this没有任何用处,后面会将其dom元素覆盖,只为之后重复调用is()方法, // 而不必生成新的jQuery对象 jqcur = jQuery(this); jqcur.context = this; // 从触发事件的元素开始,一直向根节点搜索匹配selector的元素,并执行事件处理函数 for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #xxxx) // 不处理元素为disabled的click事件 if ( cur.disabled !== true || event.type !== "click" ) { // 匹配的selector的缓存,避免每次调用is()方法进行判断 selMatch = {}; //暂存匹配selector的事件处理器 matches = []; // 将其转换为jQuery对象,后面便可以调用is()方法 jqcur[0] = cur; // 遍历代理handlers,查找匹配它们的selector for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; sel = handleObj.selector; if ( selMatch[ sel ] === undefined ) { // 调用is方法,看是否匹配当前元素 selMatch[ sel ] = jqcur.is( sel ); } // 如果匹配,那么放入matches里暂存 if ( selMatch[ sel ] ) { matches.push( handleObj ); } } if ( matches.length ) { // 将dom元素和对应的代理事件处理器以对象的形式统一添加到handlerQueue(与后面的普通事件处理器是一样的) handlerQueue.push({ elem: cur, matches: matches }); } } } } // Add the remaining (directly-bound) handlers // 将dom元素和对应的普通的事件处理器(非代理)添加到处理队列(handlerQueue)中去 if ( handlers.length > delegateCount ) { handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); } // Run delegates first; they may want to stop propagation beneath us // 优先执行代理处理器,因为有可能被代理的元素作为子元素会阻止传播 for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { matched = handlerQueue[ i ]; // 正在触发事件回调的元素 event.currentTarget = matched.elem; // 依次执行改元素对应的事件回调(在没有立刻阻止传播的情况下) for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { handleObj = matched.matches[ j ]; // Triggered event must either 1) be non-exclusive and have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { // 注册事件处理器时传进来的数据 event.data = handleObj.data; // 事件对应的事件处理器对象 event.handleObj = handleObj; // 调用事件回调,特殊事件特殊处理 ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); //如果返回值不是undefined,那么赋值到event.result中 if ( ret !== undefined ) { event.result = ret; //如果返回值是false,那么阻止冒泡传播和元素的默认操作 if ( ret === false ) { event.preventDefault(); event.stopPropagation(); } } } } } // Call the postDispatch hook for the mapped type if ( special.postDispatch ) { special.postDispatch.call( this, event ); } // 返回最后一次事件回调的值 return event.result; }, // Includes some event props shared by KeyEvent and MouseEvent // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), // 所谓hook就是拦截修改的意思 fixHooks: {}, keyHooks: { props: "char charCode key keyCode".split(" "), filter: function( event, original ) { // Add which for key events if ( event.which == null ) { event.which = original.charCode != null ? original.charCode : original.keyCode; } return event; } }, mouseHooks: { props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), filter: function( event, original ) { var eventDoc, doc, body, button = original.button, fromElement = original.fromElement; // Calculate pageX/Y if missing and clientX/Y available 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 ); } // Add relatedTarget, if necessary if ( !event.relatedTarget && fromElement ) { event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; } // Add which for click: 1 === left; 2 === middle; 3 === right // Note: button is not normalized, so don't use it if ( !event.which && button !== undefined ) { event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); } return event; } }, // 修正event,利用统一的jQuery.Event兼容各个浏览器 fix: function( event ) { // 如果已经fixed过,表明是jQuery.Event,则直接返回 if ( event[ jQuery.expando ] ) { return event; } // Create a writable copy of the event object and normalize some properties var i, prop, originalEvent = event, fixHook = jQuery.event.fixHooks[ event.type ] || {}, // 如果是key或者mouse事件,添加额外的属性(props) copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; event = jQuery.Event( originalEvent ); // 将浏览器原生event的属性赋值到新创建的jQuery.Event对象中去 for ( i = copy.length; i; ) { prop = copy[ --i ]; event[ prop ] = originalEvent[ prop ]; } // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) // 兼容触发事件的文档元素 // Safari2下event.target可能为undefined if ( !event.target ) { event.target = originalEvent.srcElement || document; } // Target should not be a text node (#504, Safari) // 如果是文本节点,则取其parent if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8) event.metaKey = !!event.metaKey; return fixHook.filter? fixHook.filter( event, originalEvent ) : event; }, special: { load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, focus: { delegateType: "focusin" }, blur: { delegateType: "focusout" }, beforeunload: { setup: function( data, namespaces, eventHandle ) { // We only want to do this special case on windows if ( jQuery.isWindow( this ) ) { this.onbeforeunload = eventHandle; } }, teardown: function( namespaces, eventHandle ) { if ( this.onbeforeunload === eventHandle ) { this.onbeforeunload = null; } } } }, simulate: function( type, elem, event, bubble ) { // Piggyback on a donor event to simulate a different one. // Fake originalEvent to avoid donor's stopPropagation, but if the // simulated event prevents default then we do the same on the donor. 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(); } } }; // Some plugins are using, but it's undocumented/deprecated and will be removed. // The 1.7 special event interface should provide all the hooks needed now. jQuery.event.handle = jQuery.event.dispatch; // 调用原生的浏览器方法注销事件处理器,做兼容行处理 jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } } : function( elem, type, handle ) { var name = "on" + type; if ( elem.detachEvent ) { // #8545, #7054, preventing memory leaks for custom events in IE6-8 – // detachEvent needed property on element, by name of that event, to properly expose it to GC if ( typeof elem[ name ] === "undefined" ) { elem[ name ] = null; } elem.detachEvent( name, handle ); } }; // jQuery为统一event对象而封装的Event类 jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword // 兼容jQuery.Event()实例化Event对象 if ( !(this instanceof jQuery.Event) ) { return new jQuery.Event( src, props ); } // jQuery.Event object 或者 浏览器的Event object if ( src && src.type ) { // 存储原先的Event对象 this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; // event type // event字面量,如:'click' } else { this.type = src; } // Put explicitly provided properties onto the event object // 提供的props将被extend进创建的event中作为属性 if ( props ) { jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one this.timeStamp = src && src.timeStamp || jQuery.now(); // Mark it as fixed // 标记已经fiexed了 this[ jQuery.expando ] = true; }; // 函数的形式,避免直接修改属性值 function returnFalse() { return false; } function returnTrue() { return true; } // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html // jQuery.Event对象的公用方法 jQuery.Event.prototype = { // 阻止元素默认操作,做兼容性处理 preventDefault: function() { this.isDefaultPrevented = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); // otherwise set the returnValue property of the original event to false (IE) } else { e.returnValue = false; } }, // 阻止元素冒泡传播,做兼容性处理 stopPropagation: function() { this.isPropagationStopped = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if stopPropagation exists run it on the original event if ( e.stopPropagation ) { e.stopPropagation(); } // otherwise set the cancelBubble property of the original event to true (IE) e.cancelBubble = true; }, // 立刻阻止传播,指的是立即停止与本元素相关的之后的所有事件处理器以及其他元素的事件传播 stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); }, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse }; // Create mouseenter/leave events using mouseover/out and event-time checks 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, selector = handleObj.selector; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || (related !== target && !jQuery.contains( target, related )) ) { event.type = handleObj.origType; ret = handleObj.handler.apply( this, arguments ); event.type = fix; } return ret; } }; }); // IE submit delegation if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { setup: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Lazy-add a submit handler when a descendant form may potentially be submitted jQuery.event.add( this, "click._submit keypress._submit", function( e ) { // Node name check avoids a VML-related crash in IE (#9807) var elem = e.target, form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; if ( form && !jQuery._data( form, "_submit_attached" ) ) { jQuery.event.add( form, "submit._submit", function( event ) { event._submit_bubble = true; }); jQuery._data( form, "_submit_attached", true ); } }); // return undefined since we don't need an event listener }, 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: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Remove delegated handlers; cleanData eventually reaps submit handlers attached above jQuery.event.remove( this, "._submit" ); } }; } // IE change delegation and checkbox/radio fix if ( !jQuery.support.changeBubbles ) { jQuery.event.special.change = { setup: function() { if ( rformElems.test( this.nodeName ) ) { // IE doesn't fire change on a check/radio until blur; trigger it on click // after a propertychange. Eat the blur-change in special.change.handle. // This still fires onchange a second time for check/radio after blur. 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; } // Delegated event; lazy-add a change handler on descendant inputs jQuery.event.add( this, "beforeactivate._change", function( e ) { var elem = e.target; if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) { 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, "_change_attached", true ); } }); }, handle: function( event ) { var elem = event.target; // Swallow native change events from checkbox/radio, we already triggered them above if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { return event.handleObj.handler.apply( this, arguments ); } }, teardown: function() { jQuery.event.remove( this, "._change" ); return !rformElems.test( this.nodeName ); } }; } // Create "bubbling" focus and blur events if ( !jQuery.support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler while someone wants focusin/focusout 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 ); } } }; }); } jQuery.fn.extend({ // 注册事件处理器 on: function( types, selector, data, fn, /*INTERNAL*/ one ) { var origFn, type; // Types can be a map of types/handlers // types可以为对象,是类型/函数对集合,如: // { // 'click' : fn1 // ,'mouseover' : fn2 // , ... // } if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // && selector != null // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { this.on( type, selector, data, types[ type ], one ); } return this; } // 以下是根据某些参数有无,对data和selector采取一定的判定匹配措施 if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } // 当fn赋值为false时,用返回值为false的函数替换,相当于是一个快捷键 if ( fn === false ) { fn = returnFalse; // 如果fn为空,则退出,什么都不做 } else if ( !fn ) { return this; } // one参数是内部调用的,one为1表示事件只在内部被调用一次 if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info // 创建jQuery空对象,调用off方法,传入带有信息的event参数(其中包括注册时的所有信息),在off中会对 // event对象做特殊处理,从而删除指定type的handler,保证只调用一次 jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); }); }, // 注册只能被触发一次的事件处理器 one: function( types, selector, data, fn ) { return this.on( types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { var handleObj, type; // 如果传进来的是event对象,那么进行如下处理(因为event对象包含off事件处理器需要的全部信息) if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event handleObj = types.handleObj; jQuery( types.delegateTarget ).off( handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } //types可以为对象,是类型/函数对集合,同上面的on if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; } // 只有两个参数的情况 if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } // 当fn赋值为false时,用返回值为false的函数替换,相当于是一个快捷键 if ( fn === false ) { fn = returnFalse; } return this.each(function() { jQuery.event.remove( this, types, fn, selector ); }); }, // 绑定事件,与on的区别在于不提供selector,这意味着它不支持事件代理 bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, unbind: function( types, fn ) { return this.off( types, null, fn ); }, // 与bind类似,但是它支持后绑定 live: function( types, data, fn ) { // 默认不指定上下问的情况下,代理元素是document,所以说live其实是用的事件代理实现的 jQuery( this.context ).on( types, this.selector, data, fn ); return this; }, die: function( types, fn ) { jQuery( this.context ).off( types, this.selector || "**", fn ); return this; }, // 事件代理接口,根本上就是调用on delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }, undelegate: function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); }, // 触发指定类型的事件回调函数列表 trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, // 与trigger几乎相同,不同的是,它只对jQuery集合中的第一个元素trigger,而且不冒泡,不执行默认操作 // 其内部调用trigger,用最后一个参数来区别 triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); } }, toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, guid = fn.guid || jQuery.guid++, i = 0, toggler = function( event ) { // Figure out which function to execute var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // Make sure that clicks stop event.preventDefault(); // and execute the function return args[ lastToggle ].apply( this, arguments ) || false; }; // link all the functions, so any of them can unbind this click handler toggler.guid = guid; while ( i < args.length ) { args[ i++ ].guid = guid; } return this.click( toggler ); }, // 模拟hover事件 hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } }); // 绑定和注册事件的快捷键,内部还是调用on()或者trigger() // 初始化jQuery.event.fixHooks jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { if ( fn == null ) { fn = data; data = null; } return arguments.length > 0 ? // 绑定事件 this.on( name, null, data, fn ) : // 触发事件,不提供数据data this.trigger( name ); }; // keyHooks if ( rkeyEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; } // mouseHooks if ( rmouseEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; } });
相关推荐
本文将详细解析jQuery源码中的魔术方法,帮助你更好地理解和运用这个强大的工具。 首先,我们要明确什么是魔术方法。在编程领域,魔术方法通常是指那些在特定时刻自动调用,且名字以两个下划线(__)开头的方法。在...
《jQuery EasyUI 1.1.2 源码解析及应用详解》 jQuery EasyUI 是一个基于 jQuery 的前端框架,它提供了一系列的组件,帮助开发者快速构建用户界面。在这个"jquery.easyUI.1.1.2 源码 .rar"压缩包中,我们主要关注的...
1. **选择器引擎Sizzle**:JQuery的强大力量之一就是其高效的选择器引擎,能够解析CSS1至CSS3的选择器,并快速找到匹配的DOM元素。Sizzle使用正则表达式和数组操作来实现高性能的查询。 2. **DOM操作**:包括`$...
《jQuery倒计时插件:jquery.countdown.js的解析与应用》 在Web开发中,倒计时功能常用于各种应用场景,如活动预告、产品发布、考试倒计时等。jQuery作为一款广泛使用的JavaScript库,提供了丰富的插件来扩展其功能...
**jQuery音乐播放器插件...总的来说,jPlayer是一个强大且灵活的音乐播放器插件,它的源码解析有助于我们更好地理解Web音频处理和jQuery插件的实现机制。通过学习和实践,开发者可以打造出符合自己需求的音乐播放体验。
深入理解jQuery源码有助于开发者更好地利用其API并优化性能。主要关注点包括: 1. **选择器引擎(Sizzle)**:jQuery 使用Sizzle作为其选择器引擎,它是如何快速高效地解析CSS选择器并找到匹配元素的。 2. **链式...
《锋利的jQuery第二版》是一本深入解析jQuery框架的权威书籍,它的源码rar文件包含了一系列用于教学和实践的示例代码。jQuery是一个广泛应用于Web开发中的JavaScript库,它简化了HTML文档遍历、事件处理、动画效果...
《jQuery源码解析:深入理解JavaScript库之精髓》 jQuery,作为一款广泛应用于Web开发的JavaScript库,以其简洁、易用的API深受开发者喜爱。它极大地简化了DOM操作、事件处理、动画效果以及Ajax交互等任务,使得...
《jQuery源码详细分析中文注释》是一份深入解析jQuery库源码的宝贵资源,它为开发者提供了理解这个广泛使用的JavaScript库内部工作机制的机会。jQuery以其简洁的API和强大的功能深受前端开发者的喜爱,但其背后的...
《深入解析jQuery源码——基于韩顺平讲解》 jQuery,作为一款广泛应用于Web开发的JavaScript库,以其简洁、易用的API深受开发者喜爱。它极大地简化了DOM操作、事件处理、动画效果以及Ajax交互等任务。韩顺平老师的...
《深入解析jQuery 1.11源码》 jQuery,作为JavaScript库的杰出代表,以其简洁易用的API,极大地简化了DOM操作、事件处理、动画制作和Ajax交互。jQuery 1.11版本是其1.x系列的一个稳定版本,为开发者提供了强大的...
下面将对jQuery 3.2.1的核心知识点进行深入解析。 一、jQuery简介 jQuery是一个轻量级的JavaScript库,它简化了HTML文档遍历、事件处理、动画制作以及Ajax交互。它的设计目标是使JavaScript编程变得更加简单,尤其...
《jQuery Accordion 源码解析》 在Web开发领域,jQuery库以其简洁的API和强大的功能深受开发者喜爱。其中,jQuery UI中的Accordion组件是一种常见的交互元素,它将多个内容区块折叠在一个有限的空间内,用户可以...
二、jQuery源码解析 jQuery的源码组织结构清晰,分为核心(core)、选择器(selector)、DOM操作(manipulation)、事件(events)、遍历(traversing)、效果(effects)等多个模块。源码中大量使用了闭包和原型链...
《jQuery实现的Div拖动排序插件:jQuery.sortable源码解析》 在网页开发中,用户交互体验的提升往往离不开动态元素的处理,而Div拖动排序功能就是其中之一。jQuery作为一款强大的JavaScript库,提供了丰富的插件来...
接下来,关于jQuery源码解析,学习者可能会接触到jQuery的模块化设计、事件委托、选择器引擎Sizzle、以及性能优化策略。jQuery采用模块化设计,如将选择器、遍历、事件等功能拆分成独立模块,便于维护和扩展。Sizzle...
本篇文章将详细解析“jQuery公司开户提交审核表单”这一项目,探讨其在HTML源码和网页代码中的实现方式。 首先,jQuery公司开户提交审核表单的核心在于利用jQuery库来构建一个用户友好的数据提交系统。这个系统主要...
二、jQuery 1.2.6源码解析 1. 构造函数:jQuery构造函数接受一个选择器字符串或DOM元素作为参数,创建一个jQuery对象。源码中的Sizzle选择器引擎用于解析选择器,找到对应的DOM元素。 2. 函数节流与延迟:jQuery的...
下面将详细探讨jQuery的核心知识点,并结合实例源码进行解析。 1. **选择器(Selectors)**:jQuery提供了多种选择器,如ID选择器(#id),类选择器(.class),元素选择器(element),以及组合选择器等。例如,`$("#...