背景:
做了那么多web項目,總會發現到處都是事件綁定,同一個按鈕的執行動作,也許會分布在多個js文件中。
而且對於js動態生成的文檔片段,里面會經常出現“onclick=...”之類的代碼,一到功能升級,或者代碼重構的時候,
就會發現,這個難度以及工作量,和重寫一遍沒什么區別,有時候甚至工作量更大!
基於各種情況的分析、以及以往的經驗總結,百度空間則有了一套自己的事件監聽管理機制:基於CSS class的事件監聽管理機制
方案:
1、js代碼中,不出現對某節點的事件監聽,如:$('#elm').click(function(e){})
2、html代碼中,不出現inline的事件監聽,如:<input type="button" value="OK" onclick="doSth();">
3、所有需要進行事件監聽的節點,都配置一個(或一類)class
4、每個獨立的子模塊(或頁面)有一個單獨的listener_manager.js文件
5、在listener_manager.js中,對所有的事件監聽進行集中管理,類似“任務管理器”
6、只對一個大容器(HTMLElement)進行事件綁定,獲取觸發源,判斷源是否包含了某個class,包含則觸發其對應的監聽
7、對於js動態生成的文檔片段,依然通過此方法,指定class即可;這個能完美的實現類似jQuery中的live
關於實現:
先來看一個非常重要的方法:事件的綁定與觸發
/**
* 通過css class的方式來注冊事件
* @param {HTMLElement} elmContainer 需要進行全局監聽的HTML節點
* @param {Array} arrEvent 需要監聽的事件列表
* @param {Object} classMap css-class和event-function之間的映射
* @param {Function} fnCustom 每次事件觸發后需要執行的自定義操作
*/
window.addEventMap =
function
(elmContainer,arrEvent,classMap,fnCustom){
$.each(arrEvent,
function
(i,item){
// 只對一個節點進行各種事件監聽
$(elmContainer).bind(item,
function
(evt){
// 獲取時間觸發源
var
evtTarget = evt.target || evt.srcElement;
// 對觸發源DOM進行安全性判斷
if
(!evtTarget)
return
false
;
for
(
var
className
in
classMap[item]){
// 獲取事件驅動方法
var
fnListener = classMap[item][className];
// 當前節點滿足觸發條件,則觸發事件
if
((evtTarget.className && $(evtTarget).hasClass(className)) ) {
fnListener.call(evtTarget,evt);
break
;
}
// 如果其父節點滿足,也可以觸發該事件
else
if
(ancestor = $(evtTarget).parents(
'.'
+ className)[0]){
fnListener.call(ancestor,evt);
break
;
}
}
//支持自定義操作
if
(
typeof
fnCustom ===
'function'
){
fnCustom.call(evt);
}
});
});
};
|
拿一個簡單的應用來舉例說明,先看listener_manager.js的內容:
/**
* 注冊命名空間
*/
window.registNS(
'qhome'
);
/**
* 事件監聽程序
* @return {[type]}
*/
qhome.ListenerMgr = (
function
(){
/**
* 展開所有轉發理由
* @param {[type]} e [description]
* @return {[type]}
*/
var
_fn_a_expand_reson =
function
(e){
};
/**
* 隱藏轉發理由
* @param {[type]} e [description]
* @return {[type]}
*/
var
_fn_a_collapse_reason =
function
(e){
};
/**
* 音樂播放
* @return {[type]}
*/
var
_fn_play_music =
function
(e){
};
/**
* 評論
*/
var
_fn_a_reply =
function
(evt){
};
/**
* 轉載
*/
var
_fn_a_repost =
function
(evt){
};
/**
* 鼠標在頭像上划過時,顯示:上傳頭像
* @param {[type]} e [description]
* @return {[type]}
*/
var
_fn_wrapper_avatar_over =
function
(e){
};
/**
* 鼠標在頭像上划出時,隱藏:上傳頭像
* @param {[type]} e [description]
* @return {[type]}
*/
var
_fn_wrapper_avatar_out =
function
(e){
};
/**
* 在這里通過DOM節點的className來對應該節點需要增加的事件監聽
*/
var
_className2ListenerMap = {
click : {
'a-expand-reason'
: _fn_a_expand_reson,
'a-collapse-reason'
: _fn_a_collapse_reason,
'q-play-music'
: _fn_play_music,
'q-progressbar'
: _fn_play_music,
'a-reply'
: _fn_a_reply,
'a-repost'
: _fn_a_repost
},
mouseover : {
'wraper-avatar'
: _fn_wrapper_avatar_over
},
mouseout : {
'wraper-avatar'
: _fn_wrapper_avatar_out
}
};
/**
* 啟動事件監聽管理器
* @return {[type]}
*/
var
_run =
function
(){
window.addEventMap(
$(
'.mod-page-main'
),
//需要進行事件監聽的容器
[
'click'
,
'mouseover'
,
'mouseout'
],
//event列表
_className2ListenerMap
//class映射表
);
};
return
{
run : _run
};
})();
|
關於window.registNS,在這里有講到。
從上面的事件監聽管理器中可以很容易的看出,每一個(或一類)CSS class,唯一對應一個監聽程序。
如上代碼中第66到81行,就是定義CSS class和event function之間的映射關系。
如上代碼中第70行和71行,class為q-play-music,以及class為q-progressbar的兩個節點,當發生click事件的時候,其具體動作都可以交由_fn_play_music處理。而且由js動態生成的節點中,也會包含class為這兩個的節點,其事件監聽就會自動的被ListenerMgr捕獲並處理,這個地方也就是jQuery中的live方式。
如上代碼中第88行就是調用了一個核心方法,用戶事件綁定。
在web應用需要初始化的時候,即可調用事件監聽管理器的run方法,啟動事件監聽管理器:
// 啟動事件監聽管理器
qhome.ListenerMgr.run();
|
這個時候,監聽器即開始工作,只要頁面上有上面動靜,符合規則的節點都會被捕獲到。
收益:
這種事件監聽管理的機制,能將web應用中所有的事件監聽進行統一管理,其初始化的入口,有且僅有一個,其作為一個單獨的plugin而存在。代碼集中,功能獨立,便於管理,維護成本低。
這是一種集中式的事件管理機制。
轉自Alien的筆記:http://www.baidufe.com/item/98947f853cc68032af53.html