jQuery 的 Event 模块非常强大。其功能远远比原生事件监听器强大许多,对同一个元素的监听只用一个 eventListener, 内部则是一个强大的观察者,根据匹配事件类型触发相应回调。jQuery 不仅封装了兼容性差异,还提供了命名空间式注册注销事件,灵活的事件委托(事件代理),手动触发事件 trigger 以及自定义事件。因为 jQuery 提供的 bind,delegate,live(1.9 版本废除了)的功能都是通过 on 来适配的,所以这里只讲 on,off,trigger。
1. 注册事件 $.fn.on 方法
on: function(types, selector, data, fn, /*INTERNAL*/ one) {
var type, origFn;
// 添加多个事件注册
if (typeof types === "object") {
// ( types-Object, selector, data )
if (typeof selector !== "string") {
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
// 为每个事件迭代
for (type in types) {
this.on(type, selector, data, types[type], one);
}
return this;
}
// 如果data和fn都为空,则将selector赋值给fn,
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;
}
}
if (fn === false) {
fn = returnFalse;
} else if (!fn) {
return this;
}
// 如果只是一次性事件,则将fn从新包装
if (one === 1) {
origFn = fn;
fn = function(event) {
// 这里使用空的jq对象来解除事件绑定信息,
// 具体定位是通过event.handleObj和目标元素event.delegateTarget
jQuery().off(event);
// 执行原始的fn函数
return origFn.apply(this, arguments);
};
// Use same guid so caller can remove using origFn
// 备忘信息
fn.guid = origFn.guid || (origFn.guid = jQuery.guid++);
}
// 统一调用jQuery.event.add方法添加事件处理
return this.each(function() {
jQuery.event.add(this, types, fn, data, selector);
});
}
可以从源码看出前面都是针对其他高层 api 做的参数调整,最后都会调用 jQuery.event.add 这个方法来注册事件。
jQuery.event.add 方法:
/**
* 事件绑定最后都通过jQuery.event.add来实现。其执行过程大致如下:
1. 先调用jQuery._data从$.cache中取出已有的事件缓存(私有数据,Cache的解析详见数据缓存)
2. 如果是第一次在DOM元素上绑定该类型事件句柄,在DOM元素上绑定jQuery.event.handle,作为统一的事件响应入口
3. 将封装后的事件句柄放入缓存中
传入的事件句柄,会被封装到对象handleObj的handle属性上,此外handleObj还会填充guid、type、namespace、data属性;DOM事件句柄elemData.handle指向jQuery.event.handle,即jQuery在DOM元素上绑定事件时总是绑定同样的DOM事件句柄jQuery.event.handle。
事件句柄在缓存$.cache中的数据结构如下,事件类型和事件句柄都存储在属性events中,属性handle存放的执行这些事件句柄的DOM事件句柄:
elemData = {
events: {
'click' : [
{ guid: 5, type: 'click', namespace: '', data: undefined,
handle: { guid: 5, prototype: {} }
},
{ ... }
],
'keypress' : [ ... ]
},
handle: { // DOM事件句柄
elem: elem,
prototype: {}
}
}
*/
add: function(elem, types, handler, data, selector) {
var tmp, events, t, handleObjIn,
special, eventHandle, handleObj,
handlers, type, namespaces, origType,
// 创建或获取私有的缓存数据
elemData = jQuery._data(elem);
if (!elemData) {
return;
}
// 可以给jq的handler对象传参数配置
if (handler.handler) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
// 确保处理程序有唯一ID,以便查找和删除
// handler函数添加guid属性
if (!handler.guid) {
handler.guid = jQuery.guid++;
}
// 首次初始化元素的事件结构和主要处理程序
// 缓存数据elemData添加events属性对象
if (!(events = elemData.events)) {
events = elemData.events = {};
}
// elemData添加handle方法
if (!(eventHandle = elemData.handle)) {
// 当我们使用jQuery为元素添加事件处理程序时,
// 实际上就是调用了这个通过包装的函数,
// 而这里面就是通过jQuery.event.dispatch方法来触发的
eventHandle = elemData.handle = function(e) {
// 如果jQuery完成初始化且不存在e或者已经jQuery.event.trigger()了
// 返回派遣委托后的结果
// this指向eventHandle.elem,解决ie中注册事件this指向的问题
// 如果是IE,这里使用attachEvent监听,其事件处理程序的第一个参数就有ie的event了。
// 平时说的window.event是指在elem['on' + type] = handler;的情况
return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventHandle.elem, arguments) :
undefined;
};
// 给handle函数添加elem属性防止IE非原生内存泄露
// handle方法添加elem属性
eventHandle.elem = elem;
}
// 处理空格分离的多事件
// jQuery(...).bind("mouseover mouseout", fn);
types = (types || '').match(core_rnotwhite) || [''];
t = types.length;
while (t--) {
tmp = rtypenamespace.exec(types[t]) || [];
type = origType = tmp[1];
// 对命名空间进行排序
// click.a.c.f.d --- a.c.d.f
namespaces = (tmp[2] || '').split('.').sort();
// 事件特例(就是为一些事件类型的一些特殊情况的处理)
special = jQuery.event.special[type] || {};
// 如果有事件特例,就使用。否则还是使用原始type
type = (selector ? special.delegateType : special.bindType) || type;
// 更新事件特例的类型
special = jQuery.event.special[type] || {};
// 给handleObj添加事件处理程序相关信息,
// 如果target对象有相同属性或方法则替换为handleObj的
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 = events[type] = [];
handlers.delegateCount = 0;
// 当事件特例处理程序没有setup方法或者setup返回false时使用addEventListener/attachEvent
if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
// 给元素绑定事件处理程序,知道这里才真正添加事件处理程序
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;
}
}
// 添加元素的事件处理列表,
// 如果有selector,则用来给委托事件使用的
if (selector) {
handlers.splice(handlers.delegateCount++, 0, handleObj);
} else {
handlers.push(handleObj);
}
// 追踪哪个事件曾经被运行过
jQuery.event.global[type] = true;
}
// 防止IE内存泄露
elem = null;
},
该方法会先从 jQuery 的缓存中查找该元素是否有事件缓存了,确保一个元素只需要一个原生的 addEventListener/attachEvent。其实 jQuery.event.add 这个方法就是拼装元素事件所需数据,然后还存在缓存系统中,添加原生监听事件处理。在使用 jQuery 的方法注册事件的时候,我们来看一下 $.cache 中保存的对应的数据结构:
这是注册事件代码,注册了 click,命名空间式的 click.ns,dblclick 以及自定义的事件 custom:
$('#list').on('click', 'li', function(e){
console.log(this);
console.log(e);
})
.on('dblclick', function(e){
console.log('dblclick');
})
.on('click.ns', function(e){
console.log('ns.click');
})
.on('custom', function(e){
console.log('custom');
});
jQuery 缓存系统中对应的事件处理器数据结构:
jQuery.event.add 就是组装上面的数据结构
2 是当前元素对应的缓存 id,该对象下一级有两个对象 events 和 handle,events 保存着事件处理器,handle 就是该元素对应的唯一一个原生事件监听处理程序的回调。
events 里面保存着我们注册事件时的事件类型 key 对应的事件处理程序回调列表,回调列表里面的每一项保存着当前事件的信息,handler 就是我们注册时的回调。我们还看到每个回调列表都会保存着一个 delegateCount 属性,这是 jQuery 计算出委托事件数目,如果用了委托注册,jQuery 先会遍历你的事件类型,如果只有一个 delegateCount 就为 1,否则就为对应的事件类型个数。delegateCount 在后面触发事件时要用到。
既然已经注册好了,我们要蓄势待发了,接着是用户触发事件(用户行为的触发事件,非手动触发 trigger),用户触发事件会触发原生的事件处理程序,然后进入到我们那个元素的对应唯一入口,处罚行为主要由 jQuery.event.dispatch 来完成:
/**
* 派遣事件
* 创建jQuery的event对象来代理访问原生的event,
* 通过jQuery.event.handlers计算出委托事件处理队列handlerQueue(冒泡路径上的元素),没有委托则保存着当前元素和保存着其事件处理相关信息的对象handleObj。
* 遍历委托事件处理队列,再遍历事件处理数组,找到匹配的事件类型,如果有处理程序,就执行它。可以使用event.stopImmediatePropagation()来阻止遍历下一个事件处理数组项。如果当前元素的当前事件处理程序返回值是false或者内部使用了event.stopPropagation()。就不会遍历下一个冒泡路径上的元素了(即当前元素的父级上的元素)
* jQuery.event.special[event.type].preDispatch和jQuery.event.special[event.type].postDispatch分别是派遣事件开始和结束的钩子方法。
* @param event 原生event对象
* @returns {result|*}
*/
dispatch: function(event) {
// 从原生event中创建jq的event
event = jQuery.event.fix(event);
var i, ret, handleObj, matched, j,
handlerQueue = [],
args = core_slice.call(arguments),
// 获取元素在jQuery.cache中的events对象的type数组
handlers = (jQuery._data(this, 'events') || {})[event.type] || [],
// 事件特例
special = jQuery.event.special[event.type] || {};
// 将第一个event参数替换为jq的event
args[0] = event;
// 设置委托目标
event.delegateTarget = this;
// 如果存在preDispatch钩子,则运行该方法后退出
if (special.preDispatch && special.preDispatch.call(this, event) === false) {
return;
}
// 委托事件队列
handlerQueue = jQuery.event.handlers.call(this, event, handlers);
// 先运行委托,如果阻止了冒泡就停止循环
i = 0;
while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) {
event.currentTarget = matched.elem;
j = 0;
// 遍历当前元素的事件处理程序数组
while ((handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped()) {
// 被触发的时间不能有命名空间或者有命名空间,且被绑定的事件是命名空间的一个子集
if (!event.namespace_re || event.namespace_re.test(handleObj.namespace)) {
event.handleObj = handleObj;
event.data = handleObj.data;
// 尝试通过事件特例触发handle方法,如果没有则触发handleObj的handler方法
// mouseenter/mouseleave事件特例就是使用了该handle方法,
// 事件特例的handle方法就是相当于一个装饰者,
// 把handleObj.handler包装了起来
ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args);
// 如果ret有值且是false则阻止默认行为和冒泡
// 即return false的时候阻止默认行为和冒泡
if (ret !== undefined) {
if ((event.result = ret) === false) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}
// 运行postDispatch钩子方法
if (special.postDispatch) {
special.postDispatch.call(this, event);
}
return event.result;
},
// 处理委托事件的方法,返回一个队列,队列中每个元素有当前元素和匹配到的handler
handlers: function(event, handlers) {
var sel, handleObj, matches, i,
handlerQueue = [],
delegateCount = handlers.delegateCount,
// 当前时间元素
cur = event.target;
// 是否有委托
if (delegateCount && cur.nodeType && (!event.button || event.type !== 'click')) {
// 遍历父辈元素,直到找到委托元素this
for (; cur != this; cur = cur.parentNode || this) {
// 确保是元素且未禁用或者非点击事件
if (cur.nodeType === 1 && (cur.disabled !== true || event.type !== 'click')) {
matches = [];
// 遍历被委托事件处理程序,handlers[i]为jq的handler对象
for (i = 0; i < delegateCount; i++) {
handleObj = handlers[i];
// 当前handler的选择器字符, 加空格字符串是为了防止和Object.prototype属性冲突
sel = handleObj.selector + ' ';
// matches[sel]保存着当前元素是否在受委托元素中的标记
if (matches[sel] === undefined) {
matches[sel] = handleObj.needsContext ?
jQuery(sel, this).index(cur) >= 0 :
jQuery.find(sel, this, null, [cur]).length;
}
// 如果当前元素是在受委托元素中,则将当前handlerObj推入到matches数组中
if (matches[sel]) {
matches.push(handleObj);
}
}
// 如果matches数组有内容,则将新对象推入handlerQueue队列中
// elem保存着当前元素,handlers这保存着当前元素匹配的handlers
if (matches.length) {
handlerQueue.push({
elem: cur,
handlers: matches
});
}
}
}
}
// 如果handlers还有剩余,把剩余的部分也推入到队列中
if (delegateCount < handlers.length) {
handlerQueue.push({
elem: this,
handlers: handlers.slice(delegateCount)
});
}
return handlerQueue;
},
// 创建一个jq event对象,让其拥有和原始event一样的属性和值
fix: function(event) {
if (event[jQuery.expando]) {
return event;
}
var i, prop, copy,
type = event.type,
originalEvent = event,
fixHook = this.fixHooks[type];
// 如果fixHook不存在判断是鼠标事件还是键盘事件再指向相应的钩子对象
if (!fixHook) {
this.fixHooks[type] = fixHook =
rmouseEvent.test(type) ? this.mouseHooks :
rkeyEvent.test(type) ? this.keyHooks : {};
}
// fixHook是否有props属性,该值是一个数组,如果有则添加到jQuery.event.props中
copy = fixHook.props ? this.props.concat(fixHook.props) : this.props;
// 创建一个jQuery Event实例event,默认行为和冒泡fix
event = new jQuery.Event(originalEvent);
// 给jq event添加原始event对象的属性
i = copy.length;
while (i--) {
prop = copy[i];
event[prop] = originalEvent[prop];
}
// Support: IE<9
if (!event.target) {
event.target = originalEvent.srcElement || document;
}
// Support: Chrome 23+, Safari?
if (event.target.nodeType === 3) {
event.target = event.target.parentNode;
}
// Support: IE<9
event.metaKey = !! event.metaKey;
// 如果钩子对象有filter解决兼容方法,则返回filter后的event
return fixHook.filter ? fixHook.filter(event, originalEvent) : event;
},
jQuery.event.dispatch 的任务主要是:
首先通过 jQuery.event.fix (event) 创建 jQuery 的 event 对象来代理访问原生的 event,jQuery.event.fix 这个方法会对 event 做兼容处理。
然后通过 jQuery.event.handlers 计算出委托事件处理队列 handlerQueue(冒泡路径上的元素),没有委托则保存着当前元素和保存着其事件处理相关信息的对象 handleObj。遍历委托事件处理队列,再遍历事件处理数组,找到匹配的事件类型,如果有处理程序,就执行它。可以使用 event.stopImmediatePropagation () 来阻止遍历下一个事件处理数组项。如果当前元素的当前事件处理程序返回值是 false 或者内部使用了 event.stopPropagation ()。就不会遍历下一个冒泡路径上的元素了(即当前元素的父级上的元素)。
jQuery.event.special [event.type].preDispatch 和 jQuery.event.special [event.type].postDispatch 分别是派遣事件开始和结束的钩子方法
然后是手动触发事件 $.fn.trigger:
/**
* 1.可触发自定义事件
* 2.触发原生事件处理程序
* 1).通过jQuery定义的
* 2).如果触发该类型事件都会触发elem[type]和elem['on' + type]方法,如果没有冒泡阻止,也会触发其他冒泡路径上的元素的ontype方法
*
* @param event
* @param data
* @param elem
* @param onlyHandlers
* @returns {*}
*/
trigger: function(event, data, elem, onlyHandlers) {
var handle, ontype, cur,
bubbleType, special, tmp, i,
eventPath = [elem || document],
type = core_hasOwn.call(event, 'type') ? event.type : event,
namespaces = core_hasOwn.call(event, 'namespace') ? event.namespace.split('.') : [];
cur = tmp = elem = elem || document;
if (elem.nodeType === 3 || elem.nodeType === 8) {
return;
}
// focus/blur变形为focusin/out,确保我们不会立刻触发它们
if (rfocusMorph.test(type + jQuery.event.triggered)) {
return;
}
if (type.indexOf('.') >= 0) {
namespaces = type.split('.');
// 取出第一项,事件类型
type = namespaces.shift();
// 命名空间排序
namespaces.sort();
}
ontype = type.indexOf(':') < 0 && 'on' + type;
// 确保是jQuery的event对象
event = event[jQuery.expando] ?
event :
new jQuery.Event(type, typeof event === 'object' && event);
event.isTrigger = true;
event.namespace = namespaces.join('.');
event.namespace_re = event.namespace ?
new RegExp('(^|\\.)' + namespaces.join('\\.(?:.*\\.|)') + '(\\.|$)') :
null;
// 清除事件,防止被重用
event.result = undefined;
if (!event.target) {
event.target = elem;
}
// 克隆来源数据和预先准备事件,创建处理程序参数列表
data = data == null ?
[event] :
jQuery.makeArray(data, [event]);
// 特殊的情况下的trigger
special = jQuery.event.special[type] || {};
if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) {
return;
}
// 保存冒泡时经过的元素到eventPath中,向上冒到document,然后到window;也可能是全局ownerDocument变量
if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) {
bubbleType = special.delegateType || type;
if (!rfocusMorph.test(bubbleType + type)) {
// 如果不是focus/blur类型,将当前元素改为父节点元素
cur = cur.parentNode;
}
// 一直向上获取父辈元素并存入eventPath数组中
for (; cur; cur = cur.parentNode) {
eventPath.push(cur);
tmp = cur;
}
// 如tmp到了document,我们添加window对象
if (tmp === (elem.ownerDocument || document)) {
eventPath.push(tmp.defaultView || tmp.parentWindow || window);
}
}
// 在事件路径上触发处理程序, 如果没有阻止冒泡就会遍历eventPath,
// 如果当前元素对应的事件类型有事件处理程序,就执行它,直到到最顶元素。
// 如果阻止,在第一次遍历后就不会再遍历了。
i = 0;
while ((cur = eventPath[i++]) && !event.isPropagationStopped()) {
event.type = i > 1 ?
bubbleType :
special.bindType || type;
// jQuery 缓存中的处理程序
handle = (jQuery._data(cur, 'events') || {})[event.type] && jQuery._data(cur, 'handle');
// 如果有handle方法,执行它。这里的handle是元素绑定的事件
if (handle) {
handle.apply(cur, data);
}
// 触发原生处理程序
handle = ontype && cur[ontype];
if (handle && jQuery.acceptData(cur) && handle.apply && handle.apply(cur, data) === false) {
event.preventDefault();
}
}
event.type = type;
// 如果没有阻止默认行为动作,处理elem的type属性事件,
// 执行elem[type]处理程序但不会触发elem['on' + type]
if (!onlyHandlers && !event.isDefaultPrevented()) {
// 1.
// 1).没有special._default
// 2).有special._default,该方法的执行结果返回false
// 2.
// type不能使click且elem不能使a标签
// 3.
// elem可接受缓存
if ((!special._default || special._default.apply(elem.ownerDocument, data) === false) && !(type === 'click' && jQuery.nodeName(elem, 'a')) && jQuery.acceptData(elem)) {
if (ontype && elem[type] && !jQuery.isWindow(elem)) {
// 缓存older
tmp = elem[ontype];
// 当我们执行foo()时,不会重新触发onfoo事件
if (tmp) {
elem[ontype] = null;
}
// 防止再次触发中的相同事件,第一次触发完后jQuery.event.triggered = undefined
jQuery.event.triggered = type;
try {
// 执行方法
elem[type]();
} catch (e) {
// 隐藏元素在focus/blur时,ie9以下会奔溃
}
jQuery.event.triggered = undefined;
if (tmp) {
elem[ontype] = tmp;
}
}
}
}
return event.result;
},
trigger 会创建一个新的 jQuery Event 对象,添加一些 trigger 的附加属性,onlyHandlers 和!onlyHandlers 参数代表 triggerHandler 和 trigger 的区别。
1.trigger 会先采集冒泡路径上的元素保存到 eventPath 数组中,
2. 在没有阻止冒泡的情况下,然后遍历 eventPath,找到对应的我们注册的事件处理程序,这里分两种事件处理,jQuery 方式添加的还有原生 elem [‘on’ + type] 形式添加的,这个过程都会触发前面两种事件处理程序。
3. 在没有阻止默认行为的情况下,然后就是执行当前元素的 elem [type] 方式的事件处理程序,这种方式的事件处理程序是通过调用原生的事件注册 addEventListener/attachEvent (所以如果没阻止冒泡,它就会向上冒泡了),当然这个步骤要避免触发上一步的事件程序,即 jQuery 的原生注册接口和 ontype 形式的,通过 jQuery.event.triggered 来保存已经被触发了的标志,这样 jQuery 的原生注册接口通过判断 jQuery.event.triggered 来决定是否触发。而 ontype 形式的就先把 ontype 至为 null,执行完操作后再恢复。
而 triggerHandler 就只做了 trigger 过程中的第二步,只是 eventPath 之保存了一个元素,就是当前元素.
最后是注销事件 $.fn.off
off: function(types, selector, fn) {
var handleObj, type;
// 当传递的types是jQuery创建的event对象时
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是对象,遍历递归
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;
}
if (fn === false) {
fn = returnFalse;
}
// 统一调用jQuery.event.remove移除事件处理程序及相关信息
return this.each(function() {
jQuery.event.remove(this, types, fn, selector);
});
},
$.fn.off 实际上调用的是 jQuery.event.remove 这个方法:
/**
* 注销元素的事件或者事件集
*
* 通过jQuery.event.remove实现,其执行过程大致如下:
1. 现调用jQuery._data从缓存$.cache中取出elem对应的所有数组(内部数据,与调用jQuery.data存储的数据稍有不同
2. 如果未传入types则移除所有事件句柄,如果types是命名空间,则移除所有与命名空间匹配的事件句柄
3. 如果是多个事件,则分割后遍历
4. 如果未指定删除哪个事件句柄,则删除事件类型对应的全部句柄,或者与命名空间匹配的全部句柄
5. 如果指定了删除某个事件句柄,则删除指定的事件句柄
6. 所有的事件句柄删除,都直接在事件句柄数组jQuery._data( elem ).events[ type ]上调用splice操作
7. 最后检查事件句柄数组的长度,如果为0,或为1但要删除,则移除绑定在elem上DOM事件
8. 最后的最后,如果elem对应的所有事件句柄events都已删除,则从缓存中移走elem的内部数据
9. 在以上的各个过程,都要检查是否有特例需要处理
*/
remove: function(elem, types, handler, selector, mappedTypes) {
var j, handleObj, tmp,
origCount, t, events,
special, handlers, type,
namespaces, origType,
elemData = jQuery.hasData(elem) && jQuery._data(elem);
if (!elemData || !(events = elemData.events)) {
return;
}
types = (types || '').match(core_rnotwhite) || [''];
t = types.length;
while (t--) {
tmp = rtypenamespace.exec(types[t]) || [];
type = origType = tmp[1];
namespaces = (tmp[2] || '').split('.').sort();
// 如果没有指定type,解绑元素的所有事件(包括命名空间上的)
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];
if ((mappedTypes || origType === handleObj.origType) &&
(!handler || handler.guid === handleObj.guid) &&
(!tmp || tmp.test(handleObj.namespace)) &&
(!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) {
// 删除events事件列表中的该项
handlers.splice(j, 1);
// 如果有委托,delegateCount就减一
if (handleObj.selector) {
handlers.delegateCount--;
}
if (special.remove) {
special.remove.call(elem, handleObj);
}
}
}
// 删除通用的事件处理程序,同时避免无限递归
// 如果原始事件列表有项,经过前面的步骤长度为0
if (origCount && !handlers.length) {
if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle) === false) {
// 删除注册的侦听事件
jQuery.removeEvent(elem, type, elemData.handle);
}
// 删除events[type]属性
delete events[type];
}
}
// 如果events不再使用则删除
if (jQuery.isEmptyObject(events)) {
delete elemData.handle;
// 使用removeData检查空的和清空expando
jQuery._removeData(elem, 'events');
}
},
注销嘛,就是对我们保存在缓存系统中的对应数据进行销毁,这里不赘述了。
总结:
jQuery 的 event 模块非常的强大,我也只是讲了一般流程,它还有一些钩子对象处理浏览器兼容问题,我这里就不探讨了。希望我的讲解可以令你解惑。