jQuery event(下)
2013-01-23 08:36 Justany_WhiteSnow 阅读(4134) 评论(2) 编辑 收藏 举报前文主要介绍了添加事件监听的方法,本文则主要讲删除事件监听,以及事件模拟。
jQuery.fn.off
jQuery.fn.off = function( types, selector, fn ) { var handleObj, type; // 如果types是对象,其实现在应该说是type,并且拥有preventDefalut和handleObj if ( types && types.preventDefault && types.handleObj ) { // 通过types获取handleObj handleObj = types.handleObj; // 转成字符串来取消事件 jQuery( types.delegateTarget ).off( handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } // 如果types还是对象,那么认为其是是一个map,key对应事件名,value对应处理函数 if ( typeof types === "object" ) { // ( types-object [, selector] ) // 遍历所有type for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; } // 如果selector为false,或者selector是个函数 if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) // 等同于传进来types和fn fn = selector; selector = undefined; } if ( fn === false ) { // 如果fn是false,则定义为一个return false的函数 fn = returnFalse; } // 遍历所有元素 return this.each(function() { // 使用jQuery.event.remove删除所有事件处理 jQuery.event.remove( this, types, fn, selector ); }); };
这个方法逻辑还是比较清晰的,尝试处理各种传参方式以后,最终都是利用jQuery.event.remove来删除事件处理函数的。
jQuery.event.remove
jQuery.event.remove = function( elem, types, handler, selector, mappedTypes ) { var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, // 通过内部缓存获取对象相关数据 elemData = jQuery.hasData( elem ) && jQuery._data( elem ); // 如果没有相关缓存数据,或者缓存中没有相关处理列表,则这个对象没事件可删除 if ( !elemData || !(events = elemData.events) ) { // 退出 return; } // types可能是通过空格分隔的多个type,转成数组 types = ( types || "" ).match( core_rnotwhite ) || [""]; t = types.length; // 遍历所有type while ( t-- ) { // 分解type和namespace tmp = rtypenamespace.exec( types[t] ) || []; // 得到type type = origType = tmp[1]; // 得到namespace namespaces = ( tmp[2] || "" ).split( "." ).sort(); // 如果type是undefined,即原来的type是.xxx.xxx之类的命名空间 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 ]; // 参数mappedTypes存在或当前事件和handleObj中的当前事件相同 if ( ( mappedTypes || origType === handleObj.origType ) && // 并且参数handler不存在,或handler的ID与handleObj的ID相同 ( !handler || handler.guid === handleObj.guid ) && // 并且没有命名空间,或者是handleObj的命名空间子集 ( !tmp || tmp.test( handleObj.namespace ) ) && // 并且没有selector,或者selector与handleObj的selector相同, // 或者selector为"**"(表示任意)并且handleObj的selector存在 ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { // 全部满足则删除掉当前事件对象 handlers.splice( j, 1 ); // 如果handleObj有selector if ( handleObj.selector ) { handlers.delegateCount--; } // 如果特殊事件remove存在,则调用special.remove // 应该和special.add对应,目前应当没什么用 if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // 如果缓存中本来存在事件处理对象,且当前没有事件处理对象 // 证明全部在上面循环中删除掉了,就清除掉 // 避免潜在的特殊事件处理程序无限递归 if ( origCount && !handlers.length ) { // 则尝试用special.teardown删除事件对handle的绑定 if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { // 不成功则使用removeEventListener删除绑定 // 这里虽然还是这么写但实际上就是removeEventListener了 jQuery.removeEvent( elem, type, elemData.handle ); } // 删除缓存中对应事件处理函数列表 delete events[ type ]; } } // 如果缓存events已经空了,该对象没有任何事件绑定了 if ( jQuery.isEmptyObject( events ) ) { // 在缓存中删除handle delete elemData.handle; // 清除掉events jQuery._removeData( elem, "events" ); } };
- 实际上,主要是删除时要判断事件、处理函数、命名空间等是否匹配,匹配才能删除。
- 还有就是,如果该事件的处理函数列队空了就需要对该事件解绑定。
- 如果改时间的事件列表都空了,那么就将主处理器,事件列表都删掉。
然后剩下的解绑定函数都是由jQuery.fn.off扩展来的。
jQuery.fn.unbind
jQuery.fn.unbind: function( types, fn ) { return this.off( types, null, fn ); };
jQuery.fn.undelegate
jQuery.fn.undelegate = function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); };
jQuery.fn.trigger
jQuery.fn.trigger = function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); };
jQuery.fn.trigger方法直接调用jQuery.event.trigger来模拟发消息。
下面的jQuery.fn.triggerHandler也是通过jQuery.event.trigger来模拟发消息。
jQuery.fn.triggerHandler = function( type, data ) { var elem = this[0]; if ( elem ) { return jQuery.event.trigger( type, data, elem, true ); } };
jQuery.event.trigger
jQuery.event.trigger = function( event, data, elem, onlyHandlers ) { var i, cur, tmp, bubbleType, ontype, handle, special, // 需要触发事件的所有元素队列 eventPath = [ elem || document ], // 指定事件类型 type = event.type || event, // 事件是否有命名空间,有则分割成数组 namespaces = event.namespace ? event.namespace.split(".") : []; cur = tmp = elem = elem || document; // 对于text和comment节点不进行事件处理 if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } // 仅对focus/blur事件变种成focusin/out进行处理 // 如果浏览器原生支持focusin/out,则确保当前不触发他们 if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } // 如果type有命名空间 if ( type.indexOf(".") >= 0 ) { // 重新组装事件 namespaces = type.split("."); type = namespaces.shift(); namespaces.sort(); } // 看看是否需要改成ontype形式 ontype = type.indexOf(":") < 0 && "on" + type; // 看看这个是不是由jQuery.Event生成的实例,否则用jQuery.Event改造 event = event[ jQuery.expando ] ? event : new jQuery.Event( type, typeof event === "object" && event ); // 对event预处理 event.isTrigger = true; event.namespace = namespaces.join("."); event.namespace_re = event.namespace ? new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : null; // 清除数据,以重新使用 event.result = undefined; // 如果事件没有触发元素,则用elem代替 if ( !event.target ) { event.target = elem; } // 如果data为空,则传入处理函数的是event,否则由data和event组成 data = data == null ? [ event ] : jQuery.makeArray( data, [ event ] ); // 尝试通过特殊事件进行处理,必要时候退出函数 special = jQuery.event.special[ type ] || {}; if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // 如果需要冒泡,特殊事件不需要阻止冒泡,且elem不是window对象 if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { // 冒泡时是否需要转成别的事件(用于事件模拟) bubbleType = special.delegateType || type; // 如果不是变形来的foucusin/out事件 if ( !rfocusMorph.test( bubbleType + type ) ) { // 则定义当前元素师父节点 cur = cur.parentNode; } // 遍历自身及所有父节点 for ( ; cur; cur = cur.parentNode ) { // 推入需要触发事件的所有元素队列 eventPath.push( cur ); // 存一下循环中最后一个cur tmp = cur; } // 如果循环中最后一个cur是document,那么事件是需要最后触发到window对象上的 // 将window对象推入元素队列 if ( tmp === (elem.ownerDocument || document) ) { eventPath.push( tmp.defaultView || tmp.parentWindow || window ); } } // 触发所有该事件对应元素的事件处理器 i = 0; // 遍历所有元素,并确保事件不需要阻止冒泡 while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { // 先确定事件绑定类型是delegateType还是bindType event.type = i > 1 ? bubbleType : special.bindType || type; // 确保缓存中该元素对应事件中包含事件处理器, // 则取出主处理器(jQuery handle)来控制所有分事件处理器 handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); // 如果主处理器(jQuery handle)存在 if ( handle ) { // 触发处理器 handle.apply( cur, data ); } // 取出原生事件处理器elem.ontype // 比如click事件就是elem.onclick handle = ontype && cur[ ontype ]; // 如果原生事件处理器存在,看看需不需要阻止事件在浏览器上的默认动作 if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { event.preventDefault(); } } // 保存事件类型,因为这时候事件可能变了 event.type = type; // 如果不需要阻止默认动作,立即执行 if ( !onlyHandlers && !event.isDefaultPrevented() ) { // 尝试通过特殊事件触发默认动作 if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { // 调用一个原生的DOM方法具有相同名称的名称作为事件的目标。 // 例如对于事件click,elem.click()是触发该事件 // 并确保不对window对象阻止默认事件 if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { // 防止我们触发FOO()来触发其默认动作时,onFOO事件又触发了 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; };
模拟触发为了让事件模型在各浏览器上表现一致,花了不少的心思。
反过来说,浏览器事件模型表现不一致,真心折磨人……orz