jQuery-1.9.1源码分析系列(十) 事件系统——事件体系结构
又是一个重磅功能点。
在分析源码之前分析一下体系结构,有助于源码理解。实际上在jQuery出现之前,Dean Edwards的跨浏览器AddEvent()设计做的已经比较优秀了;而且jQuery事件系统的设计思想也是基于该思想的,所以我们先分析一下Dean Edwards前辈的事件绑定。
a. jQuery事件原型——Dean Edwards的跨浏览器AddEvent()设计
源码解读
- //事件添加方法
- function addEvent(element, type, handler) {
- //保证每个不同的事件响应函数只有唯一一个id
- if (!handler.$$guid) handler.$$guid = addEvent.guid++;
- // 给element维护一个events属性,初始化为一个空对象。
- // element.events的结构类似于 { "click": {...}, "dbclick": {...}, "change": {...} }
- if (!element.events) element.events = {};
- // 试图取出element.events中当前事件类型type对应的对象(这个对象更像数组),赋值给handlers
- //如果element.events中没有当前事件类型type对应的对象则初始化
- var handlers = element.events[type];
- if (!handlers) {
- handlers = element.events[type] = {};
- // 如果这个element已经有了一个对应的事件的响应方法,例如已经有了onclick方法
- // 就把element的onclick方法赋值给handlers的0元素,此时handlers的结构就是:
- // { 0: function(e){...} },这也是为什么addEvent.guid初始化为1的原因,预留看为0的空间;
- // 此时element.events的结构就是: { "click": { 0: function(e){...} }, /*省略其他事件类型*/ }
- if (element["on" + type]) {
- handlers[0] = element["on" + type];
- }
- }
- // 把当前的事件handler存放到handlers中,handler.$$guid = addEvent.guid++; addEvent.guid = 1; 肯定是从1开始累加的
- //因此,这是handlers的结构可能就是 { 0: function(e){...}, 1: function(){}, 2: function(){} 等等... }
- handlers[handler.$$guid] = handler;
- //下文定义了一个handleEvent(event)函数,将这个函数,绑定到element的type事件上作为事件入口。
- //说明:在element进行click时,将会触发handleEvent函数,handleEvent函数将会查找element.events,并调用相应的函数。可以把handleEvent称为“主监听函数”
- element["on" + type] = handleEvent;
- };
- //计数器
- addEvent.guid = 1;
- function removeEvent(element, type, handler) {
- // delete the event handler from the hash table
- if (element.events && element.events[type]) {
- delete element.events[type][handler.$$guid];
- }
- };
- function handleEvent(event) {
- //兼容ie
- event = event || window.event;
- //this是响应事件的节点,这个接点上有events属性(在addEvent中添加的)
- //获取节点对应事件响应函数列表
- var handlers = this.events[event.type];
- // 循环响应函数列表执行
- for (var i in handlers) {
- //保持正确的作用域,即this关键字
- this.$$handleEvent = handlers[i];
- this.$$handleEvent(event);
- }
- };
- 重新梳理一下数据结构,使用一个例子
- <input type="text" id="chua" onClick="f0();">
- function f0(){...}
- function f1(){...}
- function f2(){...}
- function f3(){...}
- var dom = document.getElementById("chua");
- addEvent(dom,"click",f1);
- addEvent(dom,"change",f1);
- addEvent(dom,"change",f2);
- addEvent(dom,"click",f3);
- addEvent(dom,"change",f3);
经过addEvent()函数之后,当前的数据结构为:
- element: {
- onclick: handleEvent(event), //click事件的主监听函数
onchage: handleEvent(event), //change事件的主监听函数
events: {- click:{//这是一个类数组
- 0: f0, //element已有的事件
- 1: f1, //下标1实际上就是f1.$$guid
- 3: f3 //下标3实际上就是f3.$$guid,需要注意的是每一个响应事件都有一个唯一的$$guid作为下标
- ...
- },
- change:{//这是一个类数组
- 1: f1,
2: f2,
3: f3- }
- }
- }
事件系统会根据调用addEvent的顺序给每个响应函数(也就是addEvent(element, type, handler)中的第三个参数handler)打上标记$$guid。源码
- //保证每个不同的事件响应函数只有唯一一个id
- if (!handler.$$guid) handler.$$guid = addEvent.guid++;
最终三个响应函数的$$guid标记分别是
f1.$$guid = 1
f2.$$guid = 2
f3.$$guid = 3
而根据源码中
- handlers[handler.$$guid] = handler;
那么某一个函数在任何事件响应函数集合中的下标位置是固定的。比如click和change事件都调用f3作为响应事件,那么f3在element.events.click以及element.events.change中的下标位置都是f3.$$guid = 3;即element.events.click[3] = element.events.change[3] = f3。
这个时候假设又新添了一个事件绑定:addEvent(dom,"focus",f3);那么element.events.focus[3] = f3;这也是对象相比于数组的方便之处,数组不可能没有下标0,1,2就直接有3了,但是对象却可以,此时3是作为对象的一个属性名称。
这样的设计,其实已经具备了jquery事件系统的雏形,包含了几个最主要的特点:
1)element上的所有事件,将保存到element.events属性中,不是直接绑定到element上;这样一个事件可以有无数个响应函数。
2)handleEvent作为element所有事件的“主监听函数”,有它统一管理element上的所有函数。
3)所有浏览器都支持element["on" + type]事件绑定方式,跨浏览器兼容。
好啦,明白了addEvent的事件结构,这个想法确实让人觉得眼前一亮。下面分析jQuery的事件结构
b. jQuery的事件结构
所有的函数添加事件都会进入jQuery.event.add函数。该函数有两个主要功能:添加事件、附加很多事件相关信息。我们直接上源码,源码思想和Dean Edwards的跨浏览器兼容事件添加处理类似。
源码分析
- add: function( elem, types, handler, data, selector ) {
- var tmp, events, t, handleObjIn,
- special, eventHandle, handleObj,
- handlers, type, namespaces, origType,
- //获取elem节点对应的缓存数据
- elemData = jQuery._data( elem );
- //没有数据或文本/注释节点不能附加事件(但是允许附加普通对象)
- if ( !elemData ) {
- return;
- }
- //调用者能通过自定义数据替换handler
- if ( handler.handler ) {
- handleObjIn = handler;
- handler = handleObjIn.handler;
- selector = handleObjIn.selector;
- }
- //确保handler函数有唯一的ID,后续会用来查找/删除这个handler函数
- if ( !handler.guid ) {
- handler.guid = jQuery.guid++;
- }
- //如果是初次进入,初始化元素的事件结构和主事件响应入口
- if ( !(events = elemData.events) ) {
- events = elemData.events = {};
- }
- if ( !(eventHandle = elemData.handle) ) {
- eventHandle = elemData.handle = function( e ) {
- //当一个事件被调用后页面已经卸载,则放弃jQuery.event.trigger()的第二个事件,
- return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
- jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
- undefined;
- };
- //将elem作为handle函数的一个特征防止ie非本地事件引起的内存泄露
- eventHandle.elem = elem;
- }
- //多个事件使用空格隔开的处理
- //如jQuery(...).bind("mouseover mouseout", fn);
- //core_rnotwhite = /\S+/g;匹配空白字符
- types = ( types || "" ).match( core_rnotwhite ) || [""];
- t = types.length;
- while ( t-- ) {
- //rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
- //获取命名空间和原型事件
- tmp = rtypenamespace.exec( types[t] ) || [];
- type = origType = tmp[1];
- namespaces = ( tmp[2] || "" ).split( "." ).sort();
- //如果事件改变其类型,使用special事件处理器来处理更改后的事件类型
- special = jQuery.event.special[ type ] || {};
- //如果选择器已定义,确定special事件API类型,否则给他一个类型
- type = ( selector ? special.delegateType : special.bindType ) || type;
- //基于新设置的类型更新special
- special = jQuery.event.special[ type ] || {};
- // handleObj贯穿整个事件处理
- handleObj = jQuery.extend({
- type: type,
- origType: origType,
- data: data,
- handler: handler,
- guid: handler.guid,
- selector: selector,
- // For use in libraries implementing .is(). We use this for POS matching in `select`
- //"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
- //whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
- //用来判断亲密关系
- needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
- namespace: namespaces.join(".")
- }, handleObjIn );
- //初次使用时初始化事件处理器队列
- if ( !(handlers = events[ type ]) ) {
- handlers = events[ type ] = [];
- handlers.delegateCount = 0;
- //非自定义事件,如果special事件处理器返回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;
- }
- }
- //将事件对象handleObj添加到元素的处理列表,代理计数递增
- if ( selector ) {
- handlers.splice( handlers.delegateCount++, 0, handleObj );
- } else {
- handlers.push( handleObj );
- }
- //跟踪那个事件曾经被使用过,用于事件优化
- jQuery.event.global[ type ] = true;
- }
- //防止ie内存泄漏
- elem = null;
- }
依然用实例来说明jQuery的事件结构
- <div id="#center"></div>
- <script>
- function dohander(){console.log("dohander")};
- function dot(){console.log("dot");}
- $(document).on("click",'#center',dohander)
- .on("click",'#center',dot)
- .on("click",dot);
- </script>
经过添加处理环节,事件添加到了元素上,而且节点对应的缓存数据也添加了相应的数据。结构如下
- elemData = jQuery._data( elem );
- elemData = {
- events: {
- click: {//Array[3]
- 0: {
- data: undefined/{...},
- guid: 2, //处理函数的id
- handler: function dohander(){…},
- namespace: "",
- needsContext: false,
- origType: "click",
- selector: "#center",//选择器,用来区分不同事件源
- type: "click"
- }
- 1: {
- data: undefined/{...},
- guid: 3,
- handler: function dot(){…},
- namespace: "",
- needsContext: false,
- origType: "click",
- selector: "#center",
- type: "click"
- }
- 2: {
- data: undefined,
- guid: 3,
- handler: function dot(){…},
- namespace: "",
- needsContext: false,
- origType: "click",
- selector: undefined,
- type: "click"
- }
- delegateCount: 2,//委托事件数量,有selector的才是委托事件
- length: 3
- }
- }
- handle: function ( e ) {…}/*事件处理主入口*/{
- elem: document//属于handle对象的特征
- }
- }
jQuery的处理和Dean Edwards的跨浏览器兼容事件添加处理类似,比如为每一个函数添加guid;使用events对象存放响应事件列表,有一个总的事件处理入口handle等。
jQuery做了哪些改进?
1)事件数据不再直接保存在节点上,而是使用jQuery缓存系统内(内部使用的缓存jQuery._data方式存取)
2)事件委托:绑定到当前节点(例子中当前节点是document根节点)的处理函数不仅仅包含当前节点触发事件(click)响应时处理的事件(例子中selector为undefined时对应的处理函数dot);还代理了其他节点(例子中的#center节点)触发事件(click)响应时处理的事件(例子中selector为"#center"对应的处理事件doHandler和dot);委托机制在后续分析。
3)增加了很多功能数据,比如命名空间namespace:这个主要用在自定义事件自定义触发,比如$(document).on("chua.click",'#center',dot),主动触发$("#center").trigger("chua.click")。还有额外数据data:虽然没有看到那个地方有被用到。
到此jQuery的事件结构就清楚了。后面再分析事件的绑定和触发以及委托原理。
如果觉得本文不错,请点击右下方【推荐】!
jQuery-1.9.1源码分析系列(十) 事件系统——事件体系结构的更多相关文章
- 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 ...
- jQuery源码分析系列(转载来源Aaron.)
声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...
- jQuery源码分析系列——来自Aaron
jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...
- jQuery-1.9.1源码分析系列完毕目录整理
jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...
- jquery2源码分析系列
学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...
- MyCat源码分析系列之——结果合并
更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...
- MyCat源码分析系列之——SQL下发
更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...
- MyCat源码分析系列之——BufferPool与缓存机制
更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...
- MyCat源码分析系列之——前后端验证
更多MyCat源码分析,请戳MyCat源码分析系列 MyCat前端验证 MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -pr ...
随机推荐
- viso
- CSS的定位
定位的基本思想:允许你定义元素框相对于其正常位置应该出现的位置,或者相对于父元素.另一个元素甚至浏览器窗口本身的位置 一切皆为框 div.h1 或 p 元素常常被称为块级元素 ...
- ReactJS入门(二)—— 组件的生命周期
如果你熟悉avalon,使用过 data-include-rendered 和 data-include-loaded 等回调方法,那么你会很好地理解React组件的各个生命周期. 说白了其实就是Re ...
- CI-持续集成(1)-软件工业“流水线”概述
CI-持续集成(1)-软件工业“流水线”概述 1 概述 持续集成(Continuous integration)是一种软件开发实践,即团队开发成员经常集成它们的工作,通过每个成员每天至少集成一次, ...
- Unity3D游戏开发初探—2.初步了解3D模型基础
一.什么是3D模型? 1.1 3D模型概述 简而言之,3D模型就是三维的.立体的模型,D是英文Dimensions的缩写. 3D模型也可以说是用3Ds MAX建造的立体模型,包括各种建筑.人物.植被. ...
- Redis 主从配置和参数详解
安装redis 下载redis wget http://download.redis.io/releases/redis-3.0.7.tar.gz 解压redis tar -xvf redis-.ta ...
- ASP.NET Web API标准的“管道式”设计
ASP.NET Web API的核心框架是一个消息处理管道,这个管道是一组HttpMessageHandler的有序组合.这是一个双工管道,请求消息从一端流入并依次经过所有HttpMessageHan ...
- Tomcat7基于Redis的Session共享实战二
目前,为了使web能适应大规模的访问,需要实现应用的集群部署.集群最有效的方案就是负载均衡,而实现负载均衡用户每一个请求都有可能被分配到不固定的服务器上,这样我们首先要解决session的统一来保证无 ...
- iOS-C基础
iOS开发系列--C语言之基础知识 概览 当前移动开发的趋势已经势不可挡,这个系列希望浅谈一下个人对IOS开发的一些见解,这个IOS系列计划从几个角度去说IOS开发: C语言 OC基础 IOS开发(i ...
- weblogic10内存溢出解决方法
在开发过程中经常会遇到weblogic内存溢出问题,用下面的办法解决了. 找到domain/bin下的setDomainEnv.cmd文件,里面可以找到以下四行代码,将值该打一倍,重启服务. set ...