jquery現在的事件API:on,off,trigger支持帶命名空間的事件,當事件有了命名空間,就可以有效地管理同一事件的不同監聽器,在定義組件的時候,能夠避免同一元素應用到不同組件時,同一事件類型之間的影響,還能控制一些意外的事件冒泡。在實際工作中,相信大家都用的很多,但是不一定瞭解它的所有細 ...
jquery現在的事件API:on,off,trigger支持帶命名空間的事件,當事件有了命名空間,就可以有效地管理同一事件的不同監聽器,在定義組件的時候,能夠避免同一元素應用到不同組件時,同一事件類型之間的影響,還能控制一些意外的事件冒泡。在實際工作中,相信大家都用的很多,但是不一定瞭解它的所有細節,至少我有這樣的經驗,經常在碰到疑惑的時候,還得重新寫例子去驗證它的相關作用,所以本文想把事件命名空間相關的細節都梳理出來,將來再犯迷糊的時候可以回來翻著看看以便加深對它的理解和運用。
在詳細瞭解命名空間之前,得先認識下什麼是自定義事件,因為命名空間可以同時應用於自定義事件和瀏覽器預設事件當中。
1. 自定義事件
我們在定義組件的時候,瀏覽器的預設事件往往不能滿足我們的要求,比如我們寫了一個樹形組件,它有一個實例方法init用來完成這個組件的初始化工作,在這個方法調用結束之後,我們通常會自定義一個init事件,以便外部可以在樹組件初始化完成之後做一些回調處理:
<script src="../js/lib/jquery.js"></script> <div id="tree"> </div> <script> var Tree = function(element, options) { var $tree = this.$tree = $(element); //監聽init事件,觸發 $tree.on('init', $.proxy(options.onInit, this)); this.init(); }; Tree.prototype.init = function() { console.log('tree init!'); this.$tree.trigger('init'); }; var tree = new Tree('#tree', { onInit: function() { console.log(this.$tree.outerHeight()); } }); </script>
以上代碼中.on('init',…)中的init就是一個類似click這樣的自定義事件,該代碼運行結果如下
自定義事件的使用就跟瀏覽器預設事件的使用沒有任何區別,就連事件冒泡和阻止事件預設行為都完全支持,唯一的區別在於:瀏覽器自帶的事件類型可以通過瀏覽器的UI線程去觸發,而自定義事件必須通過代碼來手動觸發:
2. 事件命名空間
事件命名空間類似css的類,我們在事件類型的後面通過點加名稱的方式來給事件添加命名空間:
<script> var Tree = function(element, options) { var $tree = this.$tree = $(element); //監聽init事件,觸發 $tree.on('init.my.tree', $.proxy(options.onInit, this)); this.init(); }; Tree.prototype.init = function() { console.log('tree init!'); this.$tree.trigger('init.my.tree'); }; var tree = new Tree('#tree', { onInit: function() { console.log(this.$tree.outerHeight()); } }); </script>
以上代碼中.on('init.my.tree',…)通過.my和.tree給init這個事件添加了2個命名空間,註意命名空間是類似css的類,而不是類似java中的package,所以這兩個命名空間的名稱分別是.my和.tree,而不是my和my.tree,註意命名空間的名稱前面一定要帶點,這個名稱在off的時候可以用到。在監聽和觸發事件的時候帶上命名空間,當觸髮帶命名空間的事件時,只會調用匹配該命名空間的監聽器。所以命名空間可以有效地管理同一事件的不同監聽器,尤其在定義組件的時候可以有效地保證組件內部的事件只在組件內部有效,不會影響到其它組件。
現在假設我們不用命名空間,同時定義兩個組件Tree和Dragable,並且同時對#tree這個元素做實例化,以便實現一棵可以拖動的樹:
<script> var Tree = function(element, options) { var $tree = this.$tree = $(element); //監聽init事件,觸發 $tree.on('init', $.proxy(options.onInit, this)); this.init(); }; Tree.prototype.init = function() { console.log('tree init!'); this.$tree.trigger('init'); }; var tree = new Tree('#tree', { onInit: function() { console.log(this.$tree.outerHeight()); } }); var Dragable = function(element, options) { var $element = this.$element = $(element); //監聽init事件,觸發 $element.on('init', $.proxy(options.onInit, this)); this.init(); }; Dragable.prototype.init = function() { console.log('tree init!'); this.$element.trigger('init'); }; var drag = new Dragable('#tree', { onInit: function() { console.log('start drag!'); } }); </script>
結果會發現Tree的onInit回調被調用兩次:
根本原因就是因為#tree這個元素被應用到了多個組件,在這兩個組件內部對#tree這個元素定義了同一個名稱的事件,所以後面實例化的組件在觸發該事件的時候也會導致前面實例化的組件的同一事件再次被觸發。通過命名空間就可以避免這個問題,讓組件各自的事件回調互不影響:
<script> var Tree = function(element, options) { var $tree = this.$tree = $(element); //監聽init事件,觸發 $tree.on('init.my.tree', $.proxy(options.onInit, this)); this.init(); }; Tree.prototype.init = function() { console.log('tree init!'); this.$tree.trigger('init.my.tree'); }; var tree = new Tree('#tree', { onInit: function() { console.log(this.$tree.outerHeight()); } }); var Dragable = function(element, options) { var $element = this.$element = $(element); //監聽init事件,觸發 $element.on('init.my.dragable', $.proxy(options.onInit, this)); this.init(); }; Dragable.prototype.init = function() { console.log('drag init!'); this.$element.trigger('init.my.dragable'); }; var drag = new Dragable('#tree', { onInit: function() { console.log('start drag!'); } }); </script>
這樣tree實例的onInit就不會被調用2次了:
3. 命名空間的匹配規則
在第2部分的舉例當中,觸髮帶命名空間的事件時,觸發方式是:
然後就會調用這裡監聽的回調:
如果把觸發方式改一下,不改監聽方式,改成以下三種方式的一種,結果會怎麼樣呢:
this.$element.trigger('init'); this.$element.trigger('init.dragable'); this.$element.trigger('init.my');
答案是該監聽回調依然會被調用。這個跟命名空間的匹配規則有關,為了說明這個規則,可以用以下的這個代碼來測試:
<!DOCTYPE html> <html lang="en"> <head> <title>xxxxx</title> <style type="text/css"> #parent { margin: 100px auto 0 auto; width: 600px; height: 200px; border: 1px solid #ccc; position: relative; } .log { position: absolute; left: 0; width: 100%; height: 100%; overflow: auto; } p { margin: 0; } #btns { margin: 20px auto 0 auto; width: 600px; } </style> </head> <body> <script src="../js/lib/jquery.js"></script> <div id="parent"> <div class="log"></div> </div> <div id="btns"> <button id="btn1" type="button" onclick="$p.trigger('click.n1.n2.n3.n4');">trigger('click.n1.n2.n3.n4')</button> <button id="btn2" type="button" onclick="$p.trigger('click.n1.n2.n3');">trigger('click.n1.n2.n3')</button> <button id="btn3" type="button" onclick="$p.trigger('click.n1.n2');">trigger('click.n1.n2')</button> <button id="btn4" type="button" onclick="$p.trigger('click.n1');">trigger('click.n1')</button> <button id="btn5" type="button" onclick="$p.trigger('click');">trigger('click')</button> </div> <script> function log($e, msg) { var $log = $e.find('.log'); $log.append('<p>' + msg + '</p>'); } var $p = $('#parent'); $p.on('click.n1.n2.n3.n4', function(){ log($p, 'click n1 n2 n3 n4'); }); $p.on('click.n1.n2.n3', function(){ log($p, 'click n1 n2 n3'); }); $p.on('click.n1.n2', function(){ log($p, 'click n1 n2'); }); $p.on('click.n1', function(){ log($p, 'click n1'); }); $p.on('click', function(){ log($p, 'click'); }); </script> </body> </html>
初始化效果如下:
依次點擊界面上的按鈕(不過點擊按鈕前得先刷新頁面,這樣的話各個按鈕效果才不會混在一起),界面列印的效果如下:
以上的測試代碼一共給$p元素的click事件定義了4個命名空間,然後針對不同的命名空間數量,添加了五個監聽器,通過外部的按鈕來手動觸發各個帶命名空間的事件,從最後的結果,我們能得出這樣一個規律:
1)當觸發不帶命名空間的事件時,該事件所有的監聽器都會觸發;(從最後一個按鈕的測試結果可看出)
2)當觸髮帶一個命名空間的事件時,在監聽時包含該命名空間的所有監聽器都會被觸發;(從第4個按鈕的測試結果可看出)
3)當觸髮帶多個命名空間的事件時,只有在監聽時同時包含那多個命名空間的監聽器才會被觸發;(從第2,3個按鈕的測試結果可看出)
4)只要觸髮帶命名空間的事件,該事件不帶命名空間的監聽器就不會被觸發;(從1,2,3,4個按鈕可看出)
5)2跟3其實就是一個,2是3的一種情況
這個規律完全適用於瀏覽器預設事件和自定義事件,自定義事件的測試可以用下麵的代碼,結論是一致的:
<!DOCTYPE html> <html lang="en"> <head> <title>xxxxx</title> <style type="text/css"> #parent { margin: 100px auto 0 auto; width: 600px; height: 200px; border: 1px solid #ccc; position: relative; } .log { position: absolute; left: 0; width: 100%; height: 100%; overflow: auto; } p { margin: 0; } #btns { margin: 20px auto 0 auto; width: 600px; } </style> </head> <body> <script src="../js/lib/jquery.js"></script> <div id="parent"> <div class="log"></div> </div> <div id="btns"> <button id="btn1" type="button" onclick="$p.trigger('hello.n1.n2.n3.n4');">trigger('hello.n1.n2.n3.n4')</button> <button id="btn2" type="button" onclick="$p.trigger('hello.n1.n2.n3');">trigger('hello.n1.n2.n3')</button> <button id="btn3" type="button" onclick="$p.trigger('hello.n1.n2');">trigger('hello.n1.n2')</button> <button id="btn4" type="button" onclick="$p.trigger('hello.n1');">trigger('hello.n1')</button> <button id="btn5" type="button" onclick="$p.trigger('hello');">trigger('hello')</button> </div> <script> function log($e, msg) { var $log = $e.find('.log'); $log.append('<p>' + msg + '</p>'); } var $p = $('#parent'); $p.on('hello.n1.n2.n3.n4', function(){ log($p, 'hello n1 n2 n3 n4'); }); $p.on('hello.n1.n2.n3', function(){ log($p, 'hello n1 n2 n3'); }); $p.on('hello.n1.n2', function(){ log($p, 'hello n1 n2'); }); $p.on('hello.n1', function