事件堪比javascript的心脏,页面的更新变化,几乎都是通过事件的响应去触发的,由此可见,事件在javascript的地位。
开始正文之前,很有必要补充下事件的两个概念,一个是捕获、一个是冒泡,如下图所示
这个图很好的解释了事件的执行过程,对于浏览器常见的如click、mouse事件就可以使用上图的机制,去解决现实场景的问题。
回归正文,jQuery对事件的绑定主要总结如下:
.bind()
.live()
.delegate()
.on()
以上的这些方法,最终都是调用jQuery.fn.on方法。
而jQuery自身有提供click方法,首先看看在jQuery中,click方法是如何实现的?
jQuery.each(
(
'blur focus focusin focusout resize scroll click dblclick ' +
'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave ' +
'change select submit keydown keypress keyup contextmenu'
).split(' '),
function(i, name) {
// Handle event binding
jQuery.fn[name] = function(data, fn) {
return arguments.length > 0
? this.on(name, null, data, fn)
: this.trigger(name);
};
}
);
看到了吗,就是把这样的一个个字符串的值,添加入jQuery.fn[name]中,形成了jQuery对象中的方法。
下面看一下,平常使用jQuery实现元素的点击事件
var test = $('#test');
test.click(function() {
console.log(111);
});
上面这段代码在jQuery里面发生了什么呢?
注: jQuery的事件机制是借鉴Dean Edwards
的,从源码的这句注释可知:
/*
* Helper functions for managing events -- not part of the public interface.
* Props to Dean Edwards' addEvent library for many of the ideas.
*/
继续用图说明,jQuery事件处理流程
先来看add的实现
/*
* elem 要操作的元素
* types 事件类型
* handler 事件触发的方法
* data 事件触发的方法需要的参数
* selector 委托的元素
*/
add: function(elem, types, handler, data, selector) {
var handleObjIn,
eventHandle,
tmp,
events,
t,
handleObj,
special,
handlers,
type,
namespaces,
origType,
// 获取绑定在elem上的data缓存
elemData = dataPriv.get(elem);
// Don't attach events to noData or text/comment nodes (but allow plain objects)
// 不容许出现空数据或者是文本/注释节点,容许空对象
if (!elemData) {
return;
}
// handler容许对象代替handler函数,这里的对象指的是下面提到的handleObj
// Caller can pass in an object of custom data in lieu of the handler
if (handler.handler) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
// 确认是有效的选择器
// Ensure that invalid selectors throw exceptions at attach time
// Evaluate against documentElement in case elem is a non-element node (e.g., document)
if (selector) {
jQuery.find.matchesSelector(documentElement, selector);
}
// 给handler添加一个guid属性,确保handler函数有唯一id,之后的查找或者删除可以使用它
// Make sure that the handler has a unique ID, used to find/remove it later
if (!handler.guid) {
handler.guid = jQuery.guid++;
}
// 第一次元素节点没有handler的数据属性,这边需要给元素节点初始化,给其加上events属性
// Init the element's event structure and main handler, if this is the first
if (!(events = elemData.events)) {
events = elemData.events = {};
}
// 给elemData加上handle属性
if (!(eventHandle = elemData.handle)) {
eventHandle = elemData.handle = function(e) {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== 'undefined' &&
jQuery.event.triggered !== e.type
? jQuery.event.dispatch.apply(elem, arguments)
: undefined;
};
}
// 处理用空格分开的多事件操作
// Handle multiple events separated by a space
types = (types || '').match(rnothtmlwhite) || [''];
t = types.length;
while (t--) {
tmp = rtypenamespace.exec(types[t]) || [];
type = origType = tmp[1];
namespaces = (tmp[2] || '').split('.').sort();
// There *must* be a type, no attaching namespace-only handlers
if (!type) {
continue;
}
// 得到特殊处理的事件类型
// If event changes its type, use the special event handlers for the changed type
special = jQuery.event.special[type] || {};
// 如果selector有定义,type可能不一样
// If selector defined, determine special event api type, otherwise given type
type =
(selector ? special.delegateType : special.bindType) ||
type;
// 修正special
// Update special based on newly reset type
special = jQuery.event.special[type] || {};
// 得到handleObj
// handleObj is passed to all event handlers
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
);
// 初始化elemData.events[type]
// Init the event handler queue if we're the first
if (!(handlers = events[type])) {
handlers = events[type] = [];
handlers.delegateCount = 0;
// 在没有special.setup 或者 special.setup返回false的时候,对元素绑定事件
// Only use addEventListener if the special events handler returns false
if (
!special.setup ||
special.setup.call(
elem,
data,
namespaces,
eventHandle
) === false
) {
if (elem.addEventListener) {
elem.addEventListener(type, eventHandle);
}
}
}
if (special.add) {
special.add.call(elem, handleObj);
if (!handleObj.handler.guid) {
handleObj.handler.guid = handler.guid;
}
}
// 把handleObj附加到events[type]数组。对于委托事件,delegateCount++并且添加到数组最前面。否则添加到数组最后面
// Add to the element's handler list, delegates in front
if (selector) {
handlers.splice(handlers.delegateCount++, 0, handleObj);
} else {
handlers.push(handleObj);
}
// Keep track of which events have ever been used, for event optimization
jQuery.event.global[type] = true;
}
}
这个add方法,总的来说,就是对于节点上面的data缓存数据进行添加或者修正。之后大部分的操作都是集中在这个数据缓存elemDATA上。主要的目的就是让元素绑定了事件。就是这句代码
elem.addEventListener(type, eventHandle);
下面是jQuery.events.dispatch方法讲解
dispatch: function(nativeEvent) {
// 修正事件源对象
// Make a writable jQuery.Event from the native event object
var event = jQuery.event.fix(nativeEvent);
var i,
j,
ret,
matched,
handleObj,
handlerQueue,
args = new Array(arguments.length),
// 得到节点所绑定的事件对象
handlers =
(dataPriv.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;
for (i = 1; i < arguments.length; i++) {
args[i] = arguments[i];
}
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;
}
// 得到handler的执行队列
// 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()
) {
// 不存在命名空间,或者匹配的命名空间
// If the event is namespaced, then each handler is only invoked if it is
// specially universal or its namespaces are a superset of the event's.
if (
!event.rnamespace ||
handleObj.namespace === false ||
event.rnamespace.test(handleObj.namespace)
) {
event.handleObj = handleObj;
event.data = handleObj.data;
// 执行绑定函数
ret = (
(jQuery.event.special[handleObj.origType] || {})
.handle || handleObj.handler
).apply(matched.elem, args);
// 如果是return false,阻止默认事件,阻止冒泡
if (ret !== undefined) {
if ((event.result = ret) === false) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}
// Call the postDispatch hook for the mapped type
if (special.postDispatch) {
special.postDispatch.call(this, event);
}
return event.result;
}
dispatch方法,主要是用来分配触发回调函数,通过jQuery.fn.dispatch.apply(…)从而到执行绑定函数。
这部分是对于绑定到执行了整个流程,后面会对具体的委派事件和自定义事件进行讲解,未完待续!