第七城市th7cn
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;
},