jquery的事件名稱空間詳解

發表於2016-04-05

jquery現在的事件API:on,off,trigger支援帶名稱空間的事件,當事件有了名稱空間,就可以有效地管理同一事件的不同監聽器,在定義元件的時候,能夠避免同一元素應用到不同元件時,同一事件型別之間的影響,還能控制一些意外的事件冒泡。在實際工作中,相信大家都用的很多,但是不一定了解它的所有細節,至少我有這樣的經驗,經常在碰到疑惑的時候,還得重新寫例子去驗證它的相關作用,所以本文想把事件名稱空間相關的細節都梳理出來,將來再犯迷糊的時候可以回來翻著看看以便加深對它的理解和運用。

在詳細瞭解名稱空間之前,得先認識下什麼是自定義事件,因為名稱空間可以同時應用於自定義事件和瀏覽器預設事件當中。

1. 自定義事件

我們在定義元件的時候,瀏覽器的預設事件往往不能滿足我們的要求,比如我們寫了一個樹形元件,它有一個例項方法init用來完成這個元件的初始化工作,在這個方法呼叫結束之後,我們通常會自定義一個init事件,以便外部可以在樹元件初始化完成之後做一些回撥處理:

以上程式碼中.on(‘init’,…)中的init就是一個類似click這樣的自定義事件,該程式碼執行結果如下

image
自定義事件的使用就跟瀏覽器預設事件的使用沒有任何區別,就連事件冒泡和阻止事件預設行為都完全支援,唯一的區別在於:瀏覽器自帶的事件型別可以通過瀏覽器的UI執行緒去觸發,而自定義事件必須通過程式碼來手動觸發:

image

2. 事件名稱空間

事件名稱空間類似css的類,我們在事件型別的後面通過點加名稱的方式來給事件新增名稱空間:

以上程式碼中.on(‘init.my.tree’,…)通過.my和.tree給init這個事件新增了2個名稱空間,注意名稱空間是類似css的類,而不是類似java中的package,所以這兩個名稱空間的名稱分別是.my和.tree,而不是my和my.tree,注意名稱空間的名稱前面一定要帶點,這個名稱在off的時候可以用到。在監聽和觸發事件的時候帶上名稱空間,當觸發帶名稱空間的事件時,只會呼叫匹配該名稱空間的監聽器。所以名稱空間可以有效地管理同一事件的不同監聽器,尤其在定義元件的時候可以有效地保證元件內部的事件只在元件內部有效,不會影響到其它元件。

現在假設我們不用名稱空間,同時定義兩個元件Tree和Dragable,並且同時對#tree這個元素做例項化,以便實現一棵可以拖動的樹:

結果會發現Tree的onInit回撥被呼叫兩次:
image

根本原因就是因為#tree這個元素被應用到了多個元件,在這兩個元件內部對#tree這個元素定義了同一個名稱的事件,所以後面例項化的元件在觸發該事件的時候也會導致前面例項化的元件的同一事件再次被觸發。通過名稱空間就可以避免這個問題,讓元件各自的事件回撥互不影響:

這樣tree例項的onInit就不會被呼叫2次了:

image

3. 名稱空間的匹配規則

在第2部分的舉例當中,觸發帶名稱空間的事件時,觸發方式是:
image

然後就會呼叫這裡監聽的回撥:

image

如果把觸發方式改一下,不改監聽方式,改成以下三種方式的一種,結果會怎麼樣呢:

答案是該監聽回撥依然會被呼叫。這個跟名稱空間的匹配規則有關,為了說明這個規則,可以用以下的這個程式碼來測試:

初始化效果如下:
image
依次點選介面上的按鈕(不過點選按鈕前得先重新整理頁面,這樣的話各個按鈕效果才不會混在一起),介面列印的效果如下:

image

image

image

image

image

以上的測試程式碼一共給$p元素的click事件定義了4個名稱空間,然後針對不同的名稱空間數量,新增了五個監聽器,通過外部的按鈕來手動觸發各個帶名稱空間的事件,從最後的結果,我們能得出這樣一個規律:

1)當觸發不帶名稱空間的事件時,該事件所有的監聽器都會觸發;(從最後一個按鈕的測試結果可看出)

2)當觸發帶一個名稱空間的事件時,在監聽時包含該名稱空間的所有監聽器都會被觸發;(從第4個按鈕的測試結果可看出)

3)當觸發帶多個名稱空間的事件時,只有在監聽時同時包含那多個名稱空間的監聽器才會被觸發;(從第2,3個按鈕的測試結果可看出)

4)只要觸發帶名稱空間的事件,該事件不帶名稱空間的監聽器就不會被觸發;(從1,2,3,4個按鈕可看出)

5)2跟3其實就是一個,2是3的一種情況

這個規律完全適用於瀏覽器預設事件和自定義事件,自定義事件的測試可以用下面的程式碼,結論是一致的:

4. 名稱空間的冒泡

為了說明名稱空間的冒泡機制,需要把前面的測試程式碼改一改,並且以自定義事件來說明,測試程式碼如下:

初始化效果如下:
image

在這個測試中,點選按鈕的時候觸發的並不是$p元素的事件,而是$c元素的事件,$p是$c的父元素,上圖中整個長方形容器就是$p元素,右邊的正方形容器就是$c元素。當我們依次點選上面五個按鈕的時候(還是採取重新整理一次點一個按鈕的方式),介面列印的效果如下:

image

image

image

image

image

從這個測試結果來看,我們可以得出一個結論:jquery提供的事件機制,當子元素的帶名稱空間的事件冒泡到父級元素時,會以同樣的名稱空間觸發父級元素的同一事件,為了方便起見,可以把這種冒泡機制稱為帶名稱空間的冒泡。意味著當子元素的事件冒泡到父級元素時,只有那些滿足該事件匹配規則的父級監聽器才會被呼叫。

瀏覽器預設事件的冒泡也與自定義事件的機制相同,可以用下面的程式碼測試:

需要特別注意的是:瀏覽器的預設事件能通過滑鼠或鍵盤等操作,由瀏覽器UI執行緒自動觸發的,而且只要是瀏覽器自己觸發的事件,是不會帶名稱空間的。這樣的話,只要瀏覽器在子元素自動觸發了預設事件,那麼子元素以及父元素所有的監聽器都會執行,有時候這並不一定是你期望的,所以最好在開發元件的時候始終加名稱空間來觸發或者新增監聽,這樣就能遮蔽掉瀏覽器自動觸發給元件帶來的影響。

5. 文中小結

通過第3和第4部分,可以發現jquery的事件機制,縱向是一種帶名稱空間的冒泡機制,橫向是一種按照名稱空間匹配規則的管理方式,如下圖所示:

image

綜合起來,一個元素上的某個事件監聽器如果要被觸發的話,一共有以下幾種情況:

1)直接在該元素上觸發了該事件,通過名稱空間匹配規則被觸發;
2)由子元素的相關事件冒泡到該元素,再通過匹配規則觸發;

3)如果是瀏覽器預設事件,還會由瀏覽器自動觸發,不過瀏覽器自動觸發最終還是要體現到冒泡規則和匹配規則上來。

6. off方法中的使用

jquery中在移除事件監聽的時候,有多種方式,可以不帶名稱空間只通過事件型別來移除:

也可以通過帶名稱空間的事件型別來移除:

還可以只按名稱空間來移除:

為了更清楚地說明這三種移除方式的效果和規律,可以以下程式碼來測試

初始化介面效果為:
image

在這個測試中,為$p元素的兩種事件click和hello各新增了五個監聽器,名稱空間的的設定還與前面的類似,hello事件在click事件不帶名稱空間的回撥裡被觸發,提供了9個按鈕分別用來測試不同的off事件的方式最後的結果。測試的方法是依次點選按鈕(為了不讓各個測試的結果互相影響,點選前還是得先重新整理頁面),點完按鈕後,再點選一下$p元素,就是那個灰色邊框的容器。只有第五個按鈕不需要做第二次$p元素的點選,因為它已經把$p的click事件監聽全部移除了,各個按鈕的測試結果如下:

image

結果:click.n1.n2.n3.n4的監聽沒有被呼叫,hello事件不受影響。

image

結果:click.n1.n2.n3.n4和click.n1.n2.n3的監聽沒有被呼叫,hello事件不受影響。

image

結果:click.n1.n2.n3.n4和click.n1.n2.n3和click.n1.n2的監聽沒有被呼叫,hello事件不受影響。

image

結果:click.n1.n2.n3.n4和click.n1.n2.n3和click.n1.n2和click.n1的監聽沒有被呼叫,hello事件不受影響。

image

結果:所有click事件的回撥都沒有呼叫,hello事件不受影響。

綜合以上的測試結果,可以得出的結論是:

1)當通過一個或多個名稱空間結合事件型別來移除的時候,只會把該事件的在新增監聽的時候包含那些名稱空間的監聽器移除,不會影響該事件型別的其它監聽器以及其它事件型別。比如移除click.n1.n2,會把click.n1.n2,click.n1.n2.n3還有click.n1.n2.n3.n4都移除,但是click.n1 , click 還有hello事件都不受影響。

2)當通過事件型別來移除的時候,會把該事件的所有監聽器都移除。

再看從第6個按鈕開始的測試:

image

結果:移除了click.n1.n2.n3.n4和hello.n1.n2.n3.n4,其它事件監聽不受影響。

image

結果:移除了click.n1.n2.n3.n4,click.n1.n2.n3和hello.n1.n2.n3.n4,hello.n1.n2.n3,其它事件監聽不受影響。

image

結果:移除了click.n1.n2.n3.n4,click.n1.n2.n3,click.n1.n2和hello.n1.n2.n3.n4,hello.n1.n2.n3,hello.n1.n2,其它事件監聽不受影響。

image

結果:移除了hello和click事件所有的帶名稱空間的監聽。

綜合最後這部分的測試結果,可以得出的結論是:

通過名稱空間移除監聽的時候,會影響所有的事件型別,會把所有事件型別的在新增監聽的時候包含那些名稱空間的監聽器全部移除掉。比如最後的off(.n1),就把click和hello事件的所有帶.n1這個名稱空間的監聽移除掉了。

7. 本文小結

本文花了大量的測試去了解名稱空間在事件觸發和事件冒泡以及移除監聽時候的特性,內容雖然非常之多,但是已經充分達到了本文的最終目的,就是要把名稱空間在事件管理裡面的細節都梳理清楚,文中各個部分的核心內容最後都有簡短的結論,將來有需要的時候可以直接通過結論來解除自己的疑惑,希望能給大家帶來一些幫助,謝謝閱讀:)

相關文章