天天看點

jQuery代碼優化:基本事件

jQuery對事件系統的抽象與優化也是它的一大特色。本文僅從事件系統入手,簡要分析一下jQuery為什麼提供mouseenter和mouseleave事件,它們與标準的mouseover、mouseout事件有什麼差別。

事件模型

說到事件,就要追溯到網景與微軟的“浏覽器大戰”了。當時,事件模型還沒有标準,兩家公司的實作就是事實标準。網景在Navigator中實作了 “事件捕獲”的事件系統,而微軟則在IE中實作了一個基本上相反的事件系統,叫做“事件冒泡”。這兩種系統的差別在于當事件發生時,相關元素處理(響應) 事件的優先權不同。

下面舉例說明這兩種事件機制的差別。假設文檔中有如下結構:

1

2

3

4

5

​<​

​​

​div​

​>​

​<​

​span​

​>​

​<​

​a​

​>...</​

​a​

​>​

​</​

​span​

​>​

​</​

​div​

​>​

因為這三個元素是嵌套的,是以單擊了a,實際上也就單擊了span和div。換句話說,這三個元素都應該有處理單擊事件的機會。在事件捕獲機制下, 處理這個單擊事件的優先次序是:div > span > a;而在事件冒泡機制下,處理這個單擊事件的優先次序則是:a > span > div。

後來,W3C的規範要求浏覽器同時支援捕獲和冒泡機制,并允許開發人員選擇把事件注冊到哪個階段。于是就有了下面這個注冊事件的标準方法:

​target.addEventListener(type, listener, useCapture Optional );​

其中:

  • type:字元串,表示監聽的事件類型
  • listener:監聽器對象(JavaScript函數),在指定事件發生時可以收到通知
  • useCapture:布爾值,是否注冊到捕獲階段

在實際應用開發中,為了確定與IE(因為它不支援捕獲)相容,useCapture一般都指定為false(預設值也是false)。換句話說,隻把事件注冊到冒泡階段;對于上面那個簡單的例子來說,響應順序就是:a > span > div。

冒泡的副作用

如前所述,IE的冒泡事件模型基本上成為了事實标準。但冒泡有一個副作用。

仍以前面的文檔結構為例,假設它是界面中的一個菜單項,我們希望使用者滑鼠離開div時隐藏菜單。于是,我們給div注冊了一個mouseout事 件。如果使用者滑鼠是從div離開的,那麼一切正确。而如果使用者滑鼠是從a或span離開的,問題就來了。因為由于事件冒泡,從這兩個元素開始分派的 mouseout事件都會傳播到div,進而導緻滑鼠并沒有離開div,菜單就提前隐藏了。

當然,冒泡的副作用不難避免。比如,給div内部的每個元素都注冊mouseout事件,并使用.stopPropagation()方法阻止事件 進一步傳播。對于IE,就得将事件對象的cancelBubble屬性設定為false,取消事件冒泡。不過,這仍然回到自己處理浏覽器不相容性問題的老 路上了。

優化方案

為了避免冒泡的副作用,jQuery提供了mouseenter和mouseleave事件,就使用它們來代替mouseover和mouseout吧。

下面這個摘自jQuery的内部函數withinElement,就是為mouseenter和mouseleave提供支援的。翻譯了一下注釋,僅供大家參考。

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

​// 下面這個函數用于檢測事件是否發生在另一個元素的内部​

​// 在 jQuery.event.special.mouseenter 和 mouseleave 處理程式中使用​

​var​

​ ​

​withinElement = ​

​function​

​( event ) {​

​// 檢測 mouse(over|out) 是否還在相同的父元素内​

​var​

​parent = event.relatedTarget;​

​// 設定正确的事件類型​

​event.type = event.data;​

​// Firefox 有時候會把 relatedTarget 指定一個 XUL 元素​

​// 對于這種元素,無法通路其 parentNode 屬性​

​try​

​{​

​// Chrome 也類似,雖然可以通路 parentNode 屬性​

​// 但結果卻是 null​

​if​

​( parent && parent !== document && !parent.parentNode ) {​

​return​

​;​

​}​

​// 沿 DOM 樹向上​

​while​

​( parent && parent !== ​

​this​

​) {​

​parent = parent.parentNode;​

​}​

​if​

​( parent !== ​

​this​

​) {​

​// 如果實際正好位于一個非子元素上面,那好,就處理事件​

​jQuery.event.handle.apply( ​

​this​

​, arguments );​

​}​

​// 假定已經離開了元素,因為很可能滑鼠放在了一個XUL元素上​

​} ​

​catch​

​(e) { }​

​},​

結論

在jQuery裡,可以使用mouseenter和mouseleave事件來避免事件冒泡的副作用。

繼續閱讀