jQuery技术

深入理解jQuery的Event机制(2)

字号+ 作者:H5之家 来源:H5之家 2017-06-20 08:00 我要评论( )

该方法会先从jQuery的缓存中查找该元素是否有事件缓存了,确保一个元素只需要一个原生的addEventListener/attachEvent。其实jQuery.event.add这个方法就是拼装元素事件所需数据,然后还存在缓存系统中,添加原生监

该方法会先从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 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]形式添加的,这个过程都会触发前面两种事件处理程序。


 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • jQuery 源码系列(八)data 缓存机制

    jQuery 源码系列(八)data 缓存机制

    2017-03-05 11:03

  • 《jQuery技术内幕:深入解析jQuery架构设计与实现原理》(高云)【

    《jQuery技术内幕:深入解析jQuery架构设计与实现原理》(高云)【

    2017-02-05 16:00

  • 《深入PHP与jQuery开发》┊(美) Jason Lengstorf[.PDF] 下载

    《深入PHP与jQuery开发》┊(美) Jason Lengstorf[.PDF] 下载

    2016-12-30 17:07

  • 深入学习jQuery动画控制

    深入学习jQuery动画控制

    2016-11-19 10:01

网友点评
(