js便簽筆記(5)——Dean Edwards大牛的跨瀏覽器AddEvent()設計(不知道是不是jQuery事件系統的原型)


1. 前言:

在看Aaron的jquery源碼解讀時候,看到事件系統那塊,作者提到了Dean Edwards的添加事件的設計,於是就點進去看了看。首先讓我吃驚的是,代碼非常少,寥寥幾十行,非常簡單。於是我就仔細的看了看(如果代碼太多,可能就直接不看了)。

這段代碼是Dean Edwards在2005年寫的了,那時候還沒有jquery。但是它的設計思路確實和jquery的事件系統有些相似,即便是在9年之后的今天。

於是把這段代碼仔細研究,並在此跟大家分享以下。它將幫助你更好的理解jquery的事件系統。

先把源碼粘上,:

 1 function addEvent(element, type, handler) {
 2     // assign each event handler a unique ID
 3     if (!handler.$$guid) handler.$$guid = addEvent.guid++;
 4     // create a hash table of event types for the element
 5     if (!element.events) element.events = {};
 6     // create a hash table of event handlers for each element/event pair
 7     var handlers = element.events[type];
 8     if (!handlers) {
 9         handlers = element.events[type] = {};
10         // store the existing event handler (if there is one)
11         if (element["on" + type]) {
12             handlers[0] = element["on" + type];
13         }
14     }
15     // store the event handler in the hash table
16     handlers[handler.$$guid] = handler;
17     // assign a global event handler to do all the work
18     element["on" + type] = handleEvent;
19 };
20 // a counter used to create unique IDs
21 addEvent.guid = 1;
22 
23 function removeEvent(element, type, handler) {
24     // delete the event handler from the hash table
25     if (element.events && element.events[type]) {
26         delete element.events[type][handler.$$guid];
27     }
28 };
29 
30 function handleEvent(event) {
31     // grab the event object (IE uses a global event object)
32     event = event || window.event;
33     // get a reference to the hash table of event handlers
34     var handlers = this.events[event.type];
35     // execute each event handler
36     for (var i in handlers) {
37         this.$$handleEvent = handlers[i];
38         this.$$handleEvent(event);
39     }
40 };
View Code

 

2. 該設計的優點:

Dean Edwards在文章中提到了該設計的幾個優點:

it performs no object detection  不執行對象檢測,不理解何意。。

it does not use the addeventListener/attachEvent methods  不使用addeventListener/attachEvent方法,因為這兩個方法分別由不同的瀏覽器支持,使用時候需要判斷。  但是,在Dean Edwards提供的下載代碼中,應用到了addeventListener。

it keeps the correct scope (the this keyword)  保持正確的作用域,即this關鍵字

it passes the event object correctly  正確的傳遞event對象

it is entirely cross-browser (it will probably work on IE4 and NS4)  保證瀏覽器兼容性,甚至支持IE4和NetSape4(2005年)

and from what I can tell it does not leak memory  不會出現內存泄漏

 

3. 代碼解讀:

3.1 事件添加方法addEvent():

 1         //事件添加方法
 2         function addEvent(element, type, handler) {
 3 
 4             // assign each event handler a unique ID
 5             // 為傳入的每個事件初始化一個唯一的id
 6             if (!handler.$$guid) handler.$$guid = addEvent.guid++;   //下文:addEvent.guid = 1;
 7 
 8             // create a hash table of event types for the element
 9             // 給element維護一個events屬性,初始化為一個空對象。  
10             // element.events的結構類似於 { "click": {...}, "dbclick": {...}, "change": {...} }  
11             // 即element.events是一個對象,其中每個事件類型又會對應一個對象
12             if (!element.events) element.events = {};
13 
14             // create a hash table of event handlers for each element/event pair
15             // 試圖取出element.events中當前事件類型type對應的對象,賦值給handlers
16             var handlers = element.events[type];
17             if (!handlers) {
18                 handlers = element.events[type] = {};
19                 //如果handlers是undefined,則初始化為空對象
20 
21 
22                 // store the existing event handler (if there is one)
23                 // 如果這個element已經有了一個方法,例如已經有了onclick方法
24                 // 就把element的onclick方法賦值給handlers的0元素,此時handlers的結構就是:
25                 // { 0: function(e){...} }
26                 // 此時element.events的結構就是: { "click": { 0: function(e){...} },  /*省略其他事件類型*/ } 
27                 if (element["on" + type]) {
28                     handlers[0] = element["on" + type];
29                 }
30             }
31             // store the event handler in the hash table
32             // 把當前的事件handler存放到handlers中,handler.$$guid = addEvent.guid++; addEvent.guid = 1; 肯定是從1開始累加的
33             // 因此,這是handlers的結構就是 { 0: function(e){...}, 1: function(){}, 2: function(){} 等等... }
34             handlers[handler.$$guid] = handler;
35 
36             // assign a global event handler to do all the work
37             // 下文定義了一個handleEvent(event)函數
38             // 將這個函數,綁定到element的type事件上。  說明:在element進行click時,將會觸發handleEvent函數,handleEvent函數將會查找element.events,並調用相應的函數。可以把handleEvent稱為“主監聽函數”
39             element["on" + type] = handleEvent;
40         };
41 
42         // a counter used to create unique IDs
43         addEvent.guid = 1;

以上代碼都給出了詳細的注釋,應該能看明白了。重新梳理以下數據結構,經過addEvent()函數之后,當前的數據結構為:(假如type = 'click')

 element: { onclick: handleEvent(event), /*下文定義的函數*/ events: { click:{ 0: function(){...},    /*element已有的click事件*/
                        1: function(){...}, 2: function(){...} /*.......其他事件......*/ }, change:{ /*省略*/ }, dbclick:{ /*省略*/ } } }

這樣的設計,其實已經具備了jquery事件系統的雛形,包含了兩個最主要的特點:

  • element上的所有事件,將保存到element.events屬性中,不是直接綁定到element上;
  • handleEvent作為element所有事件的“主監聽函數”,有它統一管理element上的所有函數。

接着往下看:

 

3.2 主監聽函數handleEvent(event):

 1         //主監聽函數
 2         function handleEvent(event) {
 3             // grab the event object (IE uses a global event object)
 4             // 在IE中,event需要通過window.event獲取
 5             event = event || window.event;
 6 
 7             // get a reference to the hash table of event handlers
 8             // 根據事件類型在events中獲取事件集合(events的數據結構,參考addEvent方法的注釋)
 9             var handlers = this.events[event.type];
10             // 注意!注意!  這里的this不是window,而是element對象,因為上文 element["on" + type] = handleEvent;
11             // 所以在程序執行時,handleEvent已經作為了element的一個屬性,它的作用域是element,即this === element
12 
13             // execute each event handler
14             // 循環執行handlers集合里的所有函數    另外,這里執行事件時傳遞的event,無論在什么瀏覽器下,都是正確的
15             for (var i in handlers) {
16                 this.$$handleEvent = handlers[i];
17                 this.$$handleEvent(event);
18 
19                 //此處為何要把handlers[i]賦值給this.$$handleEvent,然后在執行呢?
20                 //而不是直接執行handlers[i](event)?
21                 //跟內存泄漏有關?
22                 //我也沒看明白,大家自己思考的,知道的可以分享給大家。
23             }
24         };

以上就是主監聽函數的實現過程,都做了注釋,也不叫好理解,有個問題,已經在代碼中有黃色背景標出來了,有了解的,也麻煩分享給大家。

jquery的主監聽函數執行時候實現的比較復雜,但是思路上和這個是一樣的。

 

3.3 移除函數事件:

1        //移除函數事件
2         function removeEvent(element, type, handler) {
3             // delete the event handler from the hash table
4             // 循環element.events[type],根據handler的唯一的id,進行delete
5             if (element.events && element.events[type]) {
6                 delete element.events[type][handler.$$guid];
7             }
8         };

移除函數比較簡單,不多解釋。

 

4. 總結

這段代碼相對於jquery的事件系統,少了事件的代理,以及模擬的時間冒泡。不考慮代理,當然就簡單許多。

但是它已經點出了jquery事件系統的原型,理解它,能幫助你更好的理解jquery事件系統。

 

補充:司徒正美的新書《javascript框架設計》中專門有一節講解:11.4 Dean Edward的addEvent.js源碼分析 260頁

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM