代码简介:定义了jQuery.event命名空间,存放了事件处理底层的工具函数add-增加事件监听,remove-移除事件监听,trigger-触发事件,dispatch-事件分发等,JQ对象利用JQ原型方法添加事件时,最终都会调用到jQuer.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 );
}
};