jQuery源码分析--Event模块(2)
- 接下来就是触发事件了。事件触发后的处理函数的分发主要靠两个函数,一个jQuery.event.dispatch,一个是jQuery.event.handlers。这个dispatch会调用handlers,而handlers会返回一个数组,这个数组是符合本次事件条件的所有处理函数对象。dispatch只管执行。
那这个handlers是如何运作的呢。绑定在一个元素上面的非代理事件是肯定要被触发的,所以会全数被返回。主要是代理事件的筛选,jQuery会从触发了事件(target所指的元素)的元素一级一级的往上检查selector是否符合,符合就把它返回。这种策略和元素的事件代理会有所不同,看下面代码:<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event fun</title>
</head>
<body>
<div style="width:200px;height:600px;background-color: red;">
<div style="width:200px;height:500px;background-color: blue;">
<div style="width:200px;height:400px;background-color: green;">
<div id="a" style="width:200px;height:300px;background-color: black;">
<div style="width:200px;height:200px;background-color: yellow;"> </div>
</div>
</div>
</div>
</div>
<script src="../../jquery-2.1.1.js"></script>
<script>
$(document).on('click',function(){})
$(document).on('click','#a',function(){})
$(document).on('click','div',{
name:'qq',
age:'dd'
},function(){
console.log(1);//5次
})
var doc = $(document)
document.onclick = function(e){
console.log(1);//1次
}
</script> </body>
</html>在这个有5层的html嵌套结构中,当在最内部的div上点击一下。jQuery的事件代理会触发5次。元素的会触发1次。
上源码:handlers: function( event, handlers ) {//新的事件对象 , 该类型的监听对象数组。将当前元素的所有监听事件排成一个序列,从底到顶,然后是普通事件
var i, matches, sel, handleObj,
handlerQueue = [],//响应对象数组
delegateCount = handlers.delegateCount,//代理事件的数量
cur = event.target;//目标元素 // Find delegate handlers
// Black-hole SVG <use> instance trees (#13180)
// Avoid non-left-click bubbling in Firefox (#3861)
if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { for ( ; cur !== this; cur = cur.parentNode || this ) {//从触发了事件的目标元素,向上找,一直到代理的元素 // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
if ( cur.disabled !== true || event.type !== "click" ) {//排除不支持click的元素
matches = [];
for ( i = 0; i < delegateCount; i++ ) {
handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203)
sel = handleObj.selector + " "; if ( matches[ sel ] === undefined ) {//判断目标元素是否匹配selector的过滤
matches[ sel ] = handleObj.needsContext ?
jQuery( sel, this ).index( cur ) >= 0 :
jQuery.find( sel, this, null, [ cur ] ).length;
}
if ( matches[ sel ] ) {//匹配的话将响应对象入队。
matches.push( handleObj );
}
}
if ( matches.length ) {//如果匹配,将元素和响应对象序列入数组
handlerQueue.push({ elem: cur, handlers: matches });
}
}
}
} // Add the remaining (directly-bound) handlers
if ( delegateCount < handlers.length ) {//绑定了普通的事件,入数组 。
handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
} return handlerQueue;
},接下来就是这个dispatch了,其实也很简单,就干了3样事情。
1、检查是不是特殊的事件,如果是优先使用特殊的处理函数
2、调用函数传入jQuery事件对象。这就是为什么在处理函数内部的事件对象是jQuery的事件对象了。
3、检查上函数的返回值是不是为false,如果是就调用jQuery.Event对象的原型中的方法阻止冒泡和默认行为。
上源码:dispatch: function( event ) {//event为原生事件对象 函数作用:主监听函数 // Make a writable jQuery.Event from the native event object
event = jQuery.event.fix( event );//创建事件对象 var i, j, ret, matched, handleObj,//ret返回值,matched放置匹配过的响应对象
handlerQueue = [],//待执行队列。包括后代元素匹配的代理监听对象数组 和 当前元素上绑定的普通监听对象数组。
args = slice.call( arguments ),//把arguments转换成真正的数组
handlers = ( data_priv.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;//存储事件对象
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;
} // 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() ) {//该元素上有响应对象且没有被阻止 // Triggered event must either 1) have no namespace, or
// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {//没有传入命名空间,或者命名空间匹配 event.handleObj = handleObj;//复制
event.data = handleObj.data; 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();
}
}
}
}
} // Call the postDispatch hook for the mapped type
if ( special.postDispatch ) {//beforeUnload
special.postDispatch.call( this, event );
} return event.result;
}, - 下面到了接触事件绑定。jQuery中删除一个事件监听函数实际上就是从事件处理函数对象的数组中删除掉一个元素而已。公开的API是off,这个函数是修正参数的,然后在底层调用jQuery.event.remove来删除。可以一次过多个事件函数,因为off函数会递归的调用自己。
jQuery.event.remove函数主要遍历处理函数对象数组,然后检查一下条件
1、是否有传type参数,如果有,是否和当前处理函数对象的type是否相等
2、是否有传处理函数,如果有,和绑定的时候是否为同一个;
3、是否有传命名空间参数,如果有,是否和处理函数对象的命名空间是否相等
4、是否有传selector参数,如果有,是否和处理函数对象的selector是否相等
如果上面的条件都符合,就把当前的处理函数对象删除。上源码
off: function( types, selector, fn ) {
var handleObj, type;
if ( types && types.preventDefault && types.handleObj ) {//使用dispatched分发过的jquery处理函数对象,也就是事件正在被触发
// ( 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;
}
if ( typeof types === "object" ) {//types 是对象,用于一次性移除多个事件类型和过个监听函数
// ( types-object [, selector] )
for ( type in types ) {
this.off( type, selector, types[ type ] );
}
return this;
}
if ( selector === false || typeof selector === "function" ) {//修正参数,selector为false,或者只传入两个参数
// ( types [, fn] )
fn = selector;
selector = undefined;
}
if ( fn === false ) {//没传fn
fn = returnFalse;
}
return this.each(function() {//调用remove删除事件
jQuery.event.remove( this, types, fn, selector );
});
}, remove: function( elem, types, handler, selector, mappedTypes ) { var j, origCount, tmp,
events, t, handleObj,
special, handlers, type, namespaces, origType,
elemData = data_priv.hasData( elem ) && data_priv.get( elem ); if ( !elemData || !(events = elemData.events) ) {//没有关联的缓存数据或者事件缓存对象
return;
} // Once for each type.namespace in types; type may be omitted
types = ( types || "" ).match( rnotwhite ) || [ "" ];//转换成数组
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[t] ) || [];
type = origType = tmp[1];
namespaces = ( tmp[2] || "" ).split( "." ).sort(); // Unbind all events (on this namespace, if provided) for the element
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;//如果有传selector则修正为代理事件,否则优先考虑修正为更好的事件
handlers = events[ type ] || [];
tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );//用于检测已绑定事件和types的命名空间是否一样 // Remove matching events
origCount = j = handlers.length;
while ( j-- ) {
handleObj = handlers[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) &&//mappedTypes不为真时比较传入类型和监听对象的原始事件类型
( !handler || handler.guid === handleObj.guid ) &&//没有指定监听函数 或 指定监听函数与监听对象具有一样的id
( !tmp || tmp.test( handleObj.namespace ) ) &&//没有指定命名空间或者监听对象的命名空间具有指定的命名空间
( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {//没传入selector 或 有传入但是与监听对象的相等 或 为"**"(所有)是监听对象有selector
handlers.splice( j, 1 );//从监听对象数组中删除 if ( handleObj.selector ) {//如果删除了的是代理事件 则修正dele gateCount 以便下一次插入代理事件的正确
handlers.delegateCount--;
}
if ( special.remove ) {//有对应的修正方法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 ) {优先调用teardown移除主监听函数
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;//移除主监听函数储存数据的对象
data_priv.remove( elem, "events" );//移除缓存
}
},
jQuery源码分析--Event模块(2)的更多相关文章
- jQuery源码分析--Event模块(1)
jQuery的Event模块提供了强大的功能:事件代理,自定义事件,自定义数据等.今天记录一下它实现的原理. 我们都知道,在js的原生事件中,有事件对象和回调函数这两样东西.但是事件对象是只读的,所以 ...
- jQuery源码分析--Event模块(3)
最后剩下了事件的手动触发了.jQuery提供了两个函数trigger和triggerHandler来手动触发事件,可以触发原生事件和自定义的事件.这个触发不单只会触发有jQuery绑定事件,而且也会触 ...
- Zepto源码分析-event模块
源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...
- nginx源码分析——event模块
源码:nginx 1.12.0 一.简介 nginx是一款非常受欢迎的软件,具备高性能.模块化可定制的良好特性.之前写了一篇nginx的http模块分析的文章,主要对http处理模块进行 ...
- zepto源码分析·event模块
准备知识 事件的本质就是发布/订阅模式,dom事件也不例外:先简单说明下发布/订阅模式,dom事件api和兼容性 发布/订阅模式 所谓发布/订阅模式,用一个形象的比喻就是买房的人订阅楼房消息,售楼处发 ...
- 读Zepto源码之Event模块
Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂. 读Zepto源码 ...
- [转] jQuery源码分析-如何做jQuery源码分析
jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
- [转]jQuery源码分析系列
文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...
随机推荐
- Python 流程控制:if
语法: if 判断条件1: # 如果判断条件1成立,就执行语句1 语句1... if 判断条件1: # 如果判断条件1成立,就执行语句1,否则执行语句2 语句1... else: 语句2... if ...
- 第七篇:Logistic回归分类算法原理分析与代码实现
前言 本文将介绍机器学习分类算法中的Logistic回归分类算法并给出伪代码,Python代码实现. (说明:从本文开始,将接触到最优化算法相关的学习.旨在将这些最优化的算法用于训练出一个非线性的函数 ...
- xdebug和最重要的php调试技巧
好几年没有写PHP代码了,最近写了一些.我比较厌烦php,主要是调试麻烦,要按无数次F5,经常刷出空白. 以前调试总是依赖于在代码中加入下面两行 error_reporting(E_ALL ^ E_N ...
- 使用boch仿真器在x86 PC平台上搭建Linux0.11系统环境(windows下)
当你有机会来到这页面时 十有八九是遇到这样一个问题 执行配置文件bochsrc_fd.bxrc时出现找不到 ips的情况! 版本原因吧 将boch版本换成2.4的问题就迎刃而解了~ 简单 ...
- shell中特殊变量$0 $1 $# $$ $! $?的涵义
$0: 执行脚本的名字 $*和$@: 将所有参数返回 $#: 参数的个数 $_: 代表上一个命令的最后一个参数 $$: 代表所在命令的PID $!: 代表最后执行的后台命令的PID $?: 代表上一个 ...
- 微信小游戏5.2.2 在子项目中使用EUI制作排行榜报错 wx.getFileSystemManager not function
本来想子项目(开放数据域)想使用EUI来制作排行榜. 原5.1.11的时候是ok的.在5.2.2中,使用assetsmananger而不是res,则会报错wx.getFileSystemManager ...
- ios 使用ASIHTTPRequest来检查版本更新
- (void) alertWithTitle: (NSString *)_title_ msg:(NSString *)msg delegate:(id)_delegate cancelButton ...
- Android - 获取SD卡的内存空间大小
获取SD卡的内存空间大小 //获得SD卡空间的信息 File path=Environment.getExternalStorageDirectory(); StatFs statFs=new Sta ...
- 微信小程序 --- 页面渲染
page.wxml文件 <view>{{text}}</view> page.js 文件: //获取应用实例 const app = getApp() Page({ data: ...
- Linux系统下 Apache+PHP 环境安装搭建
一.安装Apache2.2.221.到官网下载 http://httpd.apache.org/download.cgi ,选择相应的版本 可以先下载到windows系统中,上传到linux, 也可 ...