jQuery源码分析--Event模块(2)
- 接下来就是触发事件了。事件触发后的处理函数的分发主要靠两个函数,一个jQuery.event.dispatch,一个是jQuery.event.handlers。这个dispatch会调用handlers,而handlers会返回一个数组,这个数组是符合本次事件条件的所有处理函数对象。dispatch只管执行。
那这个handlers是如何运作的呢。绑定在一个元素上面的非代理事件是肯定要被触发的,所以会全数被返回。主要是代理事件的筛选,jQuery会从触发了事件(target所指的元素)的元素一级一级的往上检查selector是否符合,符合就把它返回。这种策略和元素的事件代理会有所不同,看下面代码:<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Event fun</title> </head> <body> <div style="width:200px;height:600px;background-color: red;"> <div style="width:200px;height:500px;background-color: blue;"> <div style="width:200px;height:400px;background-color: green;"> <div id="a" style="width:200px;height:300px;background-color: black;"> <div style="width:200px;height:200px;background-color: yellow;"> </div> </div> </div> </div> </div> <script src="../../jquery-2.1.1.js"></script> <script> $(document).on('click',function(){}) $(document).on('click','#a',function(){}) $(document).on('click','div',{ name:'qq', age:'dd' },function(){ console.log(1);//5次 }) var doc = $(document) document.onclick = function(e){ console.log(1);//1次 } </script> </body> </html>
在这个有5层的html嵌套结构中,当在最内部的div上点击一下。jQuery的事件代理会触发5次。元素的会触发1次。
上源码:handlers: function( event, handlers ) {//新的事件对象 , 该类型的监听对象数组。将当前元素的所有监听事件排成一个序列,从底到顶,然后是普通事件 var i, matches, sel, handleObj, handlerQueue = [],//响应对象数组 delegateCount = handlers.delegateCount,//代理事件的数量 cur = event.target;//目标元素 // Find delegate handlers // Black-hole SVG <use> instance trees (#13180) // Avoid non-left-click bubbling in Firefox (#3861) if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { for ( ; cur !== this; cur = cur.parentNode || this ) {//从触发了事件的目标元素,向上找,一直到代理的元素 // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) if ( cur.disabled !== true || event.type !== "click" ) {//排除不支持click的元素 matches = []; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) sel = handleObj.selector + " "; if ( matches[ sel ] === undefined ) {//判断目标元素是否匹配selector的过滤 matches[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) >= 0 : jQuery.find( sel, this, null, [ cur ] ).length; } if ( matches[ sel ] ) {//匹配的话将响应对象入队。 matches.push( handleObj ); } } if ( matches.length ) {//如果匹配,将元素和响应对象序列入数组 handlerQueue.push({ elem: cur, handlers: matches }); } } } } // Add the remaining (directly-bound) handlers if ( delegateCount < handlers.length ) {//绑定了普通的事件,入数组 。 handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); } return handlerQueue; },
接下来就是这个dispatch了,其实也很简单,就干了3样事情。
1、检查是不是特殊的事件,如果是优先使用特殊的处理函数
2、调用函数传入jQuery事件对象。这就是为什么在处理函数内部的事件对象是jQuery的事件对象了。
3、检查上函数的返回值是不是为false,如果是就调用jQuery.Event对象的原型中的方法阻止冒泡和默认行为。
上源码:dispatch: function( event ) {//event为原生事件对象 函数作用:主监听函数 // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( event );//创建事件对象 var i, j, ret, matched, handleObj,//ret返回值,matched放置匹配过的响应对象 handlerQueue = [],//待执行队列。包括后代元素匹配的代理监听对象数组 和 当前元素上绑定的普通监听对象数组。 args = slice.call( arguments ),//把arguments转换成真正的数组 handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],//当前事件类型对应的监听对象数组 special = jQuery.event.special[ event.type ] || {};//获取事件的修正对象 // Use the fix-ed jQuery.Event rather than the (read-only) native event 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 handlerQueue = jQuery.event.handlers.call( this, event, handlers );//确定要执行的响应函数数组 // Run delegates first; they may want to stop propagation beneath us i = 0; while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {//队列中还有元素且没有被阻止冒泡 event.currentTarget = matched.elem;//当前的元素 j = 0; while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {//该元素上有响应对象且没有被阻止 // Triggered event must either 1) have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {//没有传入命名空间,或者命名空间匹配 event.handleObj = handleObj;//复制 event.data = handleObj.data; 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(); } } } } } // Call the postDispatch hook for the mapped type if ( special.postDispatch ) {//beforeUnload special.postDispatch.call( this, event ); } return event.result; },
- 下面到了接触事件绑定。jQuery中删除一个事件监听函数实际上就是从事件处理函数对象的数组中删除掉一个元素而已。公开的API是off,这个函数是修正参数的,然后在底层调用jQuery.event.remove来删除。可以一次过多个事件函数,因为off函数会递归的调用自己。
jQuery.event.remove函数主要遍历处理函数对象数组,然后检查一下条件
1、是否有传type参数,如果有,是否和当前处理函数对象的type是否相等
2、是否有传处理函数,如果有,和绑定的时候是否为同一个;
3、是否有传命名空间参数,如果有,是否和处理函数对象的命名空间是否相等
4、是否有传selector参数,如果有,是否和处理函数对象的selector是否相等
如果上面的条件都符合,就把当前的处理函数对象删除。
上源码
off: function( types, selector, fn ) { var handleObj, type; if ( types && types.preventDefault && types.handleObj ) {//使用dispatched分发过的jquery处理函数对象,也就是事件正在被触发 // ( 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; } if ( typeof types === "object" ) {//types 是对象,用于一次性移除多个事件类型和过个监听函数 // ( types-object [, selector] ) for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) {//修正参数,selector为false,或者只传入两个参数 // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) {//没传fn fn = returnFalse; } return this.each(function() {//调用remove删除事件 jQuery.event.remove( this, types, fn, selector ); }); }, remove: function( elem, types, handler, selector, mappedTypes ) { var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = data_priv.hasData( elem ) && data_priv.get( elem ); if ( !elemData || !(events = elemData.events) ) {//没有关联的缓存数据或者事件缓存对象 return; } // Once for each type.namespace in types; type may be omitted types = ( types || "" ).match( rnotwhite ) || [ "" ];//转换成数组 t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[t] ) || []; type = origType = tmp[1]; namespaces = ( tmp[2] || "" ).split( "." ).sort(); // Unbind all events (on this namespace, if provided) for the element 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;//如果有传selector则修正为代理事件,否则优先考虑修正为更好的事件 handlers = events[ type ] || []; tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );//用于检测已绑定事件和types的命名空间是否一样 // Remove matching events origCount = j = handlers.length; while ( j-- ) { handleObj = handlers[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) &&//mappedTypes不为真时比较传入类型和监听对象的原始事件类型 ( !handler || handler.guid === handleObj.guid ) &&//没有指定监听函数 或 指定监听函数与监听对象具有一样的id ( !tmp || tmp.test( handleObj.namespace ) ) &&//没有指定命名空间或者监听对象的命名空间具有指定的命名空间 ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {//没传入selector 或 有传入但是与监听对象的相等 或 为"**"(所有)是监听对象有selector handlers.splice( j, 1 );//从监听对象数组中删除 if ( handleObj.selector ) {//如果删除了的是代理事件 则修正dele gateCount 以便下一次插入代理事件的正确 handlers.delegateCount--; } if ( special.remove ) {//有对应的修正方法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 ) {优先调用teardown移除主监听函数 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;//移除主监听函数储存数据的对象 data_priv.remove( elem, "events" );//移除缓存 } },