jQuery事件
jQuery 事件 --- jQuery.event.trigger
这一部分在 jQuery 的事件系统里主要负责手动触发事件,包括浏览器默认行为、自定义的事件和 dom 上的 on 事件;
由于 jQuery 的事件系统基于数据缓存系统,所以这里需要自己来模拟事件的冒泡机制;
手动触发事件时的流程:
主要的方法包括.trigger()和.triggerHandler(),它们都基于内部方法 jQuery.event.trigger 来实现:
trigger: function( type, data ) { return this.each( function() { jQuery.event.trigger( type, data, this ); } ); }, triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); } },
这两个外部 API 方法主要有两个区别:
1)trigger方法会触发绑定的事件和浏览器的默认行为,而triggerHandler方法则不会触发浏览器的默认行为
2)triggerHandler方法最后会返回处理函数的返回值,而trigger则返回 jQuery 对象,所以triggerHandler是无法进行链式调用的,并且可以把它作为普通的 js 方法来使用,如:
marginTop = parseInt( sidebar.triggerHandler( "getTop", [config.id] ),10 ) - 1 + "px"
3)triggerHandler方法只会影响第一个匹配的元素
4)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 // cache: jQuery.cache // exclusive: 是否只触发没有命名空间的事件 // cur: 当前元素的祖先元素 // old: 最顶层元素 // ontype: 行内监听事件 // special: 事件修正对象 // handle: 监听函数 // eventPath: 冒泡路径数组 // bubbleType: 当前事件类型对应的冒泡事件类型 // type: 事件类型 var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType, type = event.type || event,// event 可能是自定义事件对象或自定义的对象 namespaces = [];// 命名空间数组 // focus/blur morphs to focusin/out; ensure we're not firing them right now // 如果正在触发focus/blur的浏览器默认行为,这里要先保证不触发它们 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 // jQuery.event.customEvent的用处不明 return; } /*if ( typeof event === "object" ) { event = event[ jQuery.expando ] ? event : new jQuery.Event( type, event ); } else { event = new jQuery.Event( type ) }*/ // Caller can pass in an Event, Object, or just an event type string event = typeof event === "object" ? // 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 : "";// 忽略IE的table元素带冒号的属性访问 // 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.result = undefined; if ( !event.target ) { event.target = elem;// 如果事件的 target 属性不存在,则让它指向当前的元素 } // Clone any incoming data and prepend the event, creating the handler arg list // 把 event 和 data 都处理成数组 data = data != null ? jQuery.makeArray( data ) : []; data.unshift( event ); // Allow special events to draw outside the lines // special.trigger是一个预留的方法,类似于special.preDispatch 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, special.bindType || type ] ]; // onlyHandlers为 true // special.noBubble为true( load、scroll、error 方法 ) // 当前是 window 元素 if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { bubbleType = special.delegateType || type;// 修正冒泡类型 cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;// foucs/blur后面单独处理,其它一律复制为父元素 for ( old = elem; cur; cur = cur.parentNode ) { eventPath.push( [ cur, bubbleType ] ); old = cur;// cur 最终会变为 undefined } // Only add window if we got to document (e.g., not plain obj or detached DOM) // 如果 old 是 document 对象,则按照事件规范向eventPath中添加 window 对象 if ( old === (elem.ownerDocument || document) ) { eventPath.push( [ old.defaultView || old.parentWindow || window, bubbleType ] ); } } // Fire handlers on the event path // 路径组装完成,开始触发 for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { cur = eventPath[i][0];// 元素 event.type = eventPath[i][1];// 事件类型 // 这里是在检验路径上的元素是否绑定过该类型的事件 // 如果有,则取出主监听函数 handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Note that this is a bare JS function and not a jQuery handler // 这里为什么要检测 acceptData? handle = ontype && cur[ ontype ]; if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { event.preventDefault(); } } event.type = type;// 主监听函数可能会修改 type,所以这里进行了修正 // If nobody prevented the default action, do it now // 接下来开始触发默认行为 // 肯定是不能在 a 元素上触发默认点击行为的,因为可能会跳转 if ( !onlyHandlers && !event.isDefaultPrevented() ) { // 这里的special._default同样是一个预留的方法 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) // IE9以下不在隐藏元素上触发focus/blur事件 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 // 这里借了 old 这个变量来存储元素的行内事件 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.dispatch相关片段
customEvent
customEvent: { "getData": true, "setData": true, "changeData": true }
jQuery.event.add相关片段
// Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true;
jQuery.Event()构造函数
jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !(this instanceof jQuery.Event) ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { 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 } else { this.type = src; } // Put explicitly provided properties onto the event object 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 this[ jQuery.expando ] = true; };
浙公网安备 33010602011771号