第二十三课:jQuery.event.add的原理以及源码解读
本课主要来讲解一下jQuery是如何实现它的事件系统的。
我们先来看一个问题:
如果有一个表格有100个tr元素,每个都要绑定mouseover/mouseout事件,改成事件代理的方式,可以节省99次绑定,更何况它还能监听将来添加的tr元素。这就是jQuery中的live方法。
这种机制使用的是事件冒泡机制实现的,我们把事件处理函数绑定在tr的父元素上,然后再tr上面触发的事件会冒泡到tr的父元素,因此父元素就可以触发这个事件处理函数,在事件处理函数中就可以通过这个event获取到事件源,然后对事件源tr进行处理。
不过,live方法需要对一些不冒泡的事件做一些处理,比如一些表单事件,有的只冒泡到form,有的冒泡到document,有的压根不冒泡。
对于focus,blur,change,submit,reset,select等不会冒泡的事件(有些浏览器支持,有些不支持),在标准浏览器下,我们可以设置addEventListener的最后一个参数为true(捕获)就行了,因为捕获操作的话,事件会从document到事件源,这时就能使用事件代理机制了。IE就比较麻烦了,要用focusin代替focus,focusout代替blur,selectstart代替select。change,submit,reset就复杂了,必须用其他事件来模拟,还要判断事件源的类型,selectedIndex,keyCode等相关属性。这个课题被一个叫reglib的库搞定了。jQuery就是吸取了reglib的经验,兼容了各种事件。使用live方法进行事件代理时,最好是绑定目标元素的父元素,因为绑定document的话,在IE下有时还是会失灵。
首先,来看一下jQuery.event.add的源码解读:
add = function(elem,types,handler,data,selector){
var elemData,eventHandle,events,t,tns,type,namespaces,handleObj,handleObjIn,handlers,special; //定义一系列的变量
if(elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data(elem))){
//如果元素是文本节点(IE下访问文本节点会抛错,因为事件源不能为文本节点),就直接返回。元素也不能是注释节点。事件类型和事件处理函数不能没有。如果元素不能添加自定义属性,也直接返回。如果元素elem可以添加自定义属性,会在jQuery的缓存系统中添加这个元素的缓存对象,并返回,这时elemData就是缓存系统中,以元素elem为属性的对象。
return;
}
if(handler.handler){ //如果传进来的事件处理函数是一个json对象{handler:function(){处理函数},selector:执行上下文}
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
if(!handler.guid){ //如果事件处理函数没有唯一的guid属性,就赋值一个
handler.guid = jQuery.guid ++; //jQuery.guid从1开始,累加,因此每一个事件处理函数的guid属性都是唯一的。
}
events = elemData.events; //如果此元素elem之前没有绑定过事件处理函数,它在缓存系统中以元素elem为属性的对象的events属性将是undefined
if(!events){ //也就是说,如果之前给这个元素elem绑定过事件处理函数,那么这时的events将是一个对象,不会进入if语句
elemData.events = events = {}; //设为对象
}
eventHandle = elemData.handle; //第一次绑定时,为undefined
if(!eventHandle){ //给此元素elem绑定事件处理函数 function,这个function会处理用户绑定该元素的所有事件处理函数(哪种类型的事件触发,就执行哪种事件绑定的所有事件处理函数)
elemData.handle = eventHandle = function(e){
....
}
eventHandle.elem = elem; //这个function的elem属性就是这个元素elem
}
types = jQuery.trim(hoverHack(types)).split(" "); //因为绑定事件类型时,可能传入多个事件,比如:"mouseover mouseout",需要转换成[mouseover,mouseout],hoverHack是来处理hover这个事件的,它需要转换成mouseenter和mouseleave两个事件。
for(t=0;t<types.length;t++){
....
type = types[t];
special = jQuery.event.special[type] || {}; //并不是所有的事件都能直接使用,比如:火狐下没有mousewheel,需要用DOMMouseScroll冒充
type = (selector ? special.delegateType: special.bindType) || type; //有些事件只需要在事件代理时,需要冒充。比如:focus,blur,不冒泡的,事件代理使用的就是冒泡机制,所有需要做特殊化处理。
special = jQuery.event.special[type] || {};
handleObj = jQuery.extend({
type:type, //处理后的事件类型
origType:types[t], //真正的事件类型
data:data,
handler:handler,
guid:handler.guid,
selector:selector
.....
}, handleObjIn)
handlers = events[type]; //查看此元素在缓存系统中是否有此事件类型的数组处理函数。第一次绑定此type类型的事件时,是undefined。
if(!handlers){
handlers = events[type] = []; //此数组就是用来装载此类事件的事件处理函数的
handlers.delegateCount = 0; //记录要处理的事件代理回调函数的个数
.....
if(elem.addEventListener){
elem.addEventListener(type,eventHandle,false);//给元素绑定此类型事件的事件处理函数,如果下次继续给此元素绑定此类型事件的事件处理函数,就不会调用这里,直接把事件处理函数放进events[type]数组。
}else{
elem.attachEvent("on"+type,eventHandle); //eventHandle事件处理函数就是elemData.handle方法,就是上面定义的function,里面会操作所有的事件处理函数
}
}
......
if(selector) { //如果是使用事件代理,那么就把事件描述对象放到数组的前面
handlers.splice(handlers.deletegateCount, 0 , handleObj);
}else{
handlers.push(handleObj);
}
....
}
elem = null; //防止ie内存泄露
}
add方法的目的是,将用户传递的所有参数,合成一个handleObj对象,并把这个对象放到缓存系统中。放入缓存系统时,需要遵守一定的规则,必须是elem元素在缓存系统中对应的位置,同时,针对不同的事件类型type,创建不同的事件处理函数数组(数组中的每一项就是一个事件描述对象),每个事件处理函数数组,处理相对应的事件。因此对于同一个元素,并且同一事件,它只会绑定一次,如果对元素div1绑定两次click,那么第二个的事件处理函数,将直接添加到事件处理函数数组中(div1.click = [],其中的div1不是元素本身,而是缓存系统中跟元素div1相对应的唯一的(UUID)属性对象)。
同时add方法,会给元素elem的types中的事件类型绑定一个统一的事件处理函数eventHandle,比如:给元素elem绑定click和mouseover,它们的事件处理函数都是eventHandle。只是在这个方法中,会根据事件类型的不同,触发响应的事件处理函数。比如,触发click事件,eventHandle只会执行div1.click数组中的事件处理函数,而不是执行div1.mouseover数组中的事件处理函数。
从上可知,jQuery的回调不再与元素直接挂钩,而是通过UUID访问数据缓存系统,再根据事件类型得到一组事件描述对象。
元素与数据缓存系统之间的结构图:
elem在缓存系统中唯一的对应值是elemData.它有两个属性值handle和events,handle是一个回调函数,并且它有一个elem属性指向elem元素。events是一个json对象,它里面有很多属性,每个属性都是事件的类型,比如,click,mousemove。click这种属性值是一个数组,数组中存放的是事件描述对象。同时这个数组还有一个delegateCount属性,它代表代理事件描述对象的个数。事件描述对象是一个json对象,里面有各种属性,其中handler是真正的事件处理函数。
加油!
第二十三课:jQuery.event.add的原理以及源码解读的更多相关文章
- 第二十四课:jQuery.event.remove,dispatch的源码解读
本课还是来讲解一下jQuery是如何实现它的事件系统的.这一课我们先来讲一下jQuery.event.remove的源码解读. remove方法的目的是,根据用户传参,找到事件队列,从里面把匹配的ha ...
- 2,MapReduce原理及源码解读
MapReduce原理及源码解读 目录 MapReduce原理及源码解读 一.分片 灵魂拷问:为什么要分片? 1.1 对谁分片 1.2 长度是否为0 1.3 是否可以分片 1.4 分片的大小 1.5 ...
- 并发编程(十三)—— Java 线程池 实现原理与源码深度解析 之 Executors(三)
前两篇文章讲了线程池的源码分析,再来看这篇文章就比较简单了, 本文主要讲解 Executors 这个工具类,看看长江创建线程池的几种方法. newFixedThreadPool 生成一个固定大小的线程 ...
- Future、FutureTask实现原理浅析(源码解读)
前言 最近一直在看JUC下面的一些东西,发现很多东西都是以前用过,但是真是到原理层面自己还是很欠缺. 刚好趁这段时间不太忙,回来了便一点点学习总结. 前言 最近一直在看JUC下面的一些东西,发现很多东 ...
- http-proxy-middleware使用方法和实现原理(源码解读)
本文主要讲http-proxy-middleware用法和实现原理. 一 简介 http-proxy-middleware用于后台将请求转发给其它服务器. 例如:我们当前主机A为http://loca ...
- Vue.use原理及源码解读
vue.use(plugin, arguments) 语法 参数:plugin(Function | Object) 用法: 如果vue安装的组件类型必须为Function或者是Object<b ...
- serve-index用法、实现原理(源码解读)
本文主要讲解serve-index的用法和实现原理(源代码分析). 一 说明 serve-index的功能是将文件夹中文件列表显示到浏览器中. serve-index是一个NodeJS模块,可以通过N ...
- 第二十五课:jQuery.event.trigger的源码解读
本课主要来讲解jQuery.event.trigger的源码解读. trigger = function(event, data, elem, onlyHandlers){ if(elem & ...
- NeHe OpenGL教程 第二十三课:球面映射
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
随机推荐
- hdu 2196 Computer(树形DP)
Computer Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Su ...
- 有用的MySQL语句
摘自onefish资料库 1. 计算年数你想通过生日来计算这个人有几岁了. SELECT DATE_FORMAT(FROM_DAYS(TO_DAYS(now()) - TO_DAYS(@dateofb ...
- HTML5与触摸界面
习惯了开发典型的面向电脑端浏览器的网站在开发手机端网站或者移动App的时候面对很多新的问题,这些新的问题,在我看来或多或少会给浏览者在使用网站或App的时候带来不好的用户体验,对于一个产品级应用,用户 ...
- 【温故而知新-CSS】使用CSS设计网站导航栏
body #nav li a { width: auto; } #nav li a:hover { background-color: #ffcc00; color: #fff; border-rig ...
- 深度优先搜索 codevs 1065 01字符串
codevs 1065 01字符串 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 输出仅有0和1组成的长度为n的字符串,并且 ...
- POJ 3233 Matrix Power Series --二分求矩阵等比数列和
题意:求S(k) = A+A^2+...+A^k. 解法:二分即可. if(k为奇) S(k) = S(k-1)+A^k else S(k) = S(k/2)*(I+A^(k/2)) ...
- SVN代码的回滚二
SVN代码的回滚: 不丢失新建的文件,获得最新的SVN版本控制.TortoiseSVN-ShowLog-选中你要回滚的版本-右键-Export,之后将修改的文件覆盖到你的最新版本,commit即可. ...
- java 8-6 抽象的练习
1. 猫狗案例 具体事物:猫,狗 共性:姓名,年龄,吃饭 分析:从具体到抽象 猫: 成员变量:姓名,年龄 构造方法:无参,带参 成员方法:吃饭(猫吃鱼) 狗: 成员变量:姓名,年龄 构造方法:无参,带 ...
- 关于OAuth2.0的文章收集
http://blog.csdn.net/seccloud/article/details/8192707
- 程序清单 8-8 exec函数实例,a.out是程序8-9产生的可执行程序
/* ============================================================================ Name : test.c Author ...