首页 > 语言 > JavaScript > 正文

jQuery-1.9.1源码分析系列(十)事件系统之事件体系结构

2024-05-06 16:25:12
字体:
来源:转载
供稿:网友

这篇文章主要介绍了jQuery-1.9.1源码分析系列(十)事件系统之事件体系结构的相关资料,需要的朋友可以参考下

又是一个重磅功能点。

在分析源码之前分析一下体系结构,有助于源码理解。实际上在jQuery出现之前,Dean Edwards的跨浏览器AddEvent()设计做的已经比较优秀了;而且jQuery事件系统的设计思想也是基于该思想的,所以我们先分析一下Dean Edwards前辈的事件绑定。

a. jQuery事件原型——Dean Edwards的跨浏览器AddEvent()设计

源码解读

 

 
  1. //事件添加方法 
  2. function addEvent(element, type, handler) { 
  3.   //保证每个不同的事件响应函数只有唯一一个id 
  4. if (!handler.$guid) handler.$guid = addEvent.guid++; 
  5.   // 给element维护一个events属性,初始化为一个空对象。  
  6. // element.events的结构类似于 { "click": {...}, "dbclick": {...}, "change": {...} }  
  7. if (!element.events) element.events = {}; 
  8.   // 试图取出element.events中当前事件类型type对应的对象(这个对象更像数组),赋值给handlers 
  9.   //如果element.events中没有当前事件类型type对应的对象则初始化 
  10. var handlers = element.events[type]; 
  11.   if (!handlers) { 
  12.      handlers = element.events[type] = {}; 
  13.      // 如果这个element已经有了一个对应的事件的响应方法,例如已经有了onclick方法 
  14. // 就把element的onclick方法赋值给handlers的0元素,此时handlers的结构就是: 
  15. // { 0: function(e){...} },这也是为什么addEvent.guid初始化为1的原因,预留看为0的空间; 
  16. // 此时element.events的结构就是: { "click": { 0: function(e){...} }, /*省略其他事件类型*/ }  
  17. if (element["on" + type]) { 
  18. handlers[0] = element["on" + type]; 
  19.   // 把当前的事件handler存放到handlers中,handler.$guid = addEvent.guid++; addEvent.guid = 1; 肯定是从1开始累加的  
  20.   //因此,这是handlers的结构可能就是 { 0: function(e){...}, 1: function(){}, 2: function(){} 等等... } 
  21. handlers[handler.$guid] = handler; 
  22.   //下文定义了一个handleEvent(event)函数,将这个函数,绑定到element的type事件上作为事件入口。 
  23.   //说明:在element进行click时,将会触发handleEvent函数,handleEvent函数将会查找element.events,并调用相应的函数。可以把handleEvent称为“主监听函数” 
  24. element["on" + type] = handleEvent; 
  25. }; 
  26. //计数器 
  27. addEvent.guid = 1; 
  28. function removeEvent(element, type, handler) { 
  29. // delete the event handler from the hash table 
  30. if (element.events && element.events[type]) { 
  31. delete element.events[type][handler.$guid]; 
  32. }; 
  33. function handleEvent(event) { 
  34.   //兼容ie 
  35.   event = event || window.event; 
  36.   //this是响应事件的节点,这个接点上有events属性(在addEvent中添加的) 
  37.   //获取节点对应事件响应函数列表 
  38. var handlers = this.events[event.type]; 
  39. // 循环响应函数列表执行 
  40.   for (var i in handlers) { 
  41. //保持正确的作用域,即this关键字 
  42.      this.$handleEvent = handlers[i]; 
  43. this.$handleEvent(event); 
  44. }; 

重新梳理一下数据结构,使用一个例子

 

 
  1. <input type="text" id="chua" onClick="f0();"
  2. function f0(){...} 
  3. function f1(){...} 
  4. function f2(){...} 
  5. function f3(){...} 
  6. var dom = document.getElementById("chua"); 
  7. addEvent(dom,"click",f1); 
  8. addEvent(dom,"change",f1); 
  9. addEvent(dom,"change",f2); 
  10. addEvent(dom,"click",f3); 
  11. addEvent(dom,"change",f3); 

经过addEvent()函数之后,当前的数据结构为:

 

 
  1. element: { 
  2.   onclick: handleEvent(event), //click事件的主监听函数 
  3.   onchage: handleEvent(event),  //change事件的主监听函数 
  4.   events: { 
  5.     click:{//这是一个类数组 
  6.       0: f0, //element已有的事件 
  7.       1: f1,  //下标1实际上就是f1.$guid 
  8.       3: f3 //下标3实际上就是f3.$guid,需要注意的是每一个响应事件都有一个唯一的$guid作为下标  
  9.       ... 
  10.     }, 
  11.     change:{//这是一个类数组 
  12.       1: f1, 
  13.       2: f2, 
  14.       3: f3 
  15.     } 

事件系统会根据调用addEvent的顺序给每个响应函数(也就是addEvent(element, type, handler)中的第三个参数handler)打上标记$$guid。源码

 

 
  1. //保证每个不同的事件响应函数只有唯一一个id 
  2. f (!handler.$guid) handler.$guid = addEvent.guid++; 

最终三个响应函数的$$guid标记分别是

f1.$$guid = 1

f2.$$guid = 2

f3.$$guid = 3

而根据源码中

 

 
  1. 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的跨浏览器兼容事件添加处理类似。

源码分析

 

 
  1. add: function( elem, types, handler, data, selector ) { 
  2. var tmp, events, t, handleObjIn, 
  3. special, eventHandle, handleObj, 
  4. handlers, type, namespaces, origType, 
  5. //获取elem节点对应的缓存数据 
  6. elemData = jQuery._data( elem ); 
  7. //没有数据或文本/注释节点不能附加事件(但是允许附加普通对象) 
  8. if ( !elemData ) { 
  9. return
  10. //调用者能通过自定义数据替换handler 
  11. if ( handler.handler ) { 
  12. handleObjIn = handler; 
  13. handler = handleObjIn.handler; 
  14. selector = handleObjIn.selector; 
  15. //确保handler函数有唯一的ID,后续会用来查找/删除这个handler函数 
  16. if ( !handler.guid ) { 
  17. handler.guid = jQuery.guid++; 
  18. //如果是初次进入,初始化元素的事件结构和主事件响应入口 
  19. if ( !(events = elemData.events) ) { 
  20. events = elemData.events = {}; 
  21. if ( !(eventHandle = elemData.handle) ) { 
  22. eventHandle = elemData.handle = function( e ) { 
  23. //当一个事件被调用后页面已经卸载,则放弃jQuery.event.trigger()的第二个事件, 
  24. return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? 
  25. jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : 
  26. undefined; 
  27. }; 
  28. //将elem作为handle函数的一个特征防止ie非本地事件引起的内存泄露 
  29. eventHandle.elem = elem; 
  30. //多个事件使用空格隔开的处理 
  31.   //如jQuery(...).bind("mouseover mouseout", fn); 
  32.   //core_rnotwhite = //S+/g;匹配空白字符 
  33.   types = ( types || "" ).match( core_rnotwhite ) || [""]; 
  34.   t = types.length; 
  35.   while ( t-- ) { 
  36. //rtypenamespace = /^([^.]*)(?:/.(.+)|)$/; 
  37. //获取命名空间和原型事件 
  38. tmp = rtypenamespace.exec( types[t] ) || []; 
  39. type = origType = tmp[1]; 
  40. namespaces = ( tmp[2] || "" ).split( "." ).sort(); 
  41. //如果事件改变其类型,使用special事件处理器来处理更改后的事件类型 
  42. special = jQuery.event.special[ type ] || {}; 
  43. //如果选择器已定义,确定special事件API类型,否则给他一个类型 
  44. type = ( selector ? special.delegateType : special.bindType ) || type; 
  45. //基于新设置的类型更新special 
  46. special = jQuery.event.special[ type ] || {}; 
  47. // handleObj贯穿整个事件处理 
  48. handleObj = jQuery.extend({ 
  49. type: type, 
  50. origType: origType, 
  51. data: data, 
  52. handler: handler, 
  53. guid: handler.guid, 
  54. selector: selector, 
  55. // For use in libraries implementing .is(). We use this for POS matching in `select` 
  56. //"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?://(" + 
  57. //whitespace + "*((?:-//d)?//d*)" + whitespace + "*//)|)(?=[^-]|$)", "i" ) 
  58. //用来判断亲密关系 
  59. needsContext: selector && jQuery.expr.match.needsContext.test( selector ), 
  60. namespace: namespaces.join("."
  61. }, handleObjIn ); 
  62. //初次使用时初始化事件处理器队列 
  63. if ( !(handlers = events[ type ]) ) { 
  64. handlers = events[ type ] = []; 
  65. handlers.delegateCount = 0; 
  66. //非自定义事件,如果special事件处理器返回false,则只能使用addEventListener/attachEvent 
  67. if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { 
  68. //给元素绑定全局事件 
  69. if ( elem.addEventListener ) { 
  70. elem.addEventListener( type, eventHandle, false ); 
  71. else if ( elem.attachEvent ) { 
  72. elem.attachEvent( "on" + type, eventHandle ); 
  73. //自定义事件绑定 
  74. if ( special.add ) { 
  75. special.add.call( elem, handleObj ); 
  76. if ( !handleObj.handler.guid ) { 
  77. handleObj.handler.guid = handler.guid; 
  78. //将事件对象handleObj添加到元素的处理列表,代理计数递增 
  79. if ( selector ) { 
  80. handlers.splice( handlers.delegateCount++, 0, handleObj ); 
  81. else { 
  82. handlers.push( handleObj ); 
  83. //跟踪那个事件曾经被使用过,用于事件优化 
  84. jQuery.event.global[ type ] = true
  85. //防止ie内存泄漏 
  86. elem = null

依然用实例来说明jQuery的事件结构

 

 
  1. <div id="#center"></div> 
  2. <script> 
  3. function dohander(){console.log("dohander")}; 
  4. function dot(){console.log("dot");} 
  5. $(document).on("click",'#center',dohander) 
  6. .on("click",'#center',dot) 
  7. .on("click",dot); 
  8. </script> 

经过添加处理环节,事件添加到了元素上,而且节点对应的缓存数据也添加了相应的数据。结构如下

 

 
  1. elemData = jQuery._data( elem ); 
  2. elemData = { 
  3.   events: { 
  4.     click: {//Array[3] 
  5.       0: { 
  6.         data: undefined/{...}, 
  7.         guid: 2, //处理函数的id 
  8.         handler: function dohander(){…}, 
  9.         namespace: ""
  10.         needsContext: false
  11.         origType: "click"
  12.         selector: "#center",//选择器,用来区分不同事件源 
  13.         type: "click" 
  14.       } 
  15.       1: { 
  16.         data: undefined/{...}, 
  17.         guid: 3, 
  18.         handler: function dot(){…}, 
  19.         namespace: ""
  20.         needsContext: false
  21.         origType: "click"
  22.         selector: "#center"
  23.         type: "click" 
  24.       } 
  25.       2: { 
  26.         data: undefined, 
  27.         guid: 3, 
  28.         handler: function dot(){…}, 
  29.         namespace: ""
  30.         needsContext: false
  31.         origType: "click"
  32.         selector: undefined, 
  33.         type: "click" 
  34.       } 
  35.       delegateCount: 2,//委托事件数量,有selector的才是委托事件 
  36.       length: 3 
  37.     } 
  38.   } 
  39.   handle: function ( e ) {…}/*事件处理主入口*/
  40.     elem: document//属于handle对象的特征 
  41.   } 

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的事件结构就清楚了。后面再分析事件的绑定和触发以及委托原理。


注:相关教程知识阅读请移步到JavaScript/Ajax教程频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选