国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 語言 > 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教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 德保县| 湘潭县| 新巴尔虎右旗| 岳阳市| 凉城县| 白银市| 舞钢市| 宜州市| 池州市| 徐水县| 五华县| 锦州市| 洛川县| 土默特左旗| 自治县| 贵南县| 孟连| 玛多县| 千阳县| 拜城县| 中宁县| 天全县| 汉源县| 武平县| 柯坪县| 镇赉县| 盐源县| 辽宁省| 偃师市| 玛纳斯县| 安阳县| 澄江县| 迭部县| 东乡| 苏尼特右旗| 璧山县| 容城县| 饶河县| 民勤县| 新闻| 凤山县|