JavaScript的事件廣播與偵聽
阿新 • • 發佈:2019-01-23
先來看html頁面的主要結構:
需求:id為test的輸入框(以下簡稱A)值改變時,實時改變id為left和right的內容。<div id="wrap" class="wrap"> <input type="text" name="test" id="test" size="35" /> </div> <div class="wrap" style="position:absolute;left:20px;top:250px;"> left:<div id="left" class="inner"></div> </div> <div class="wrap" style="position:absolute;left:550px;top:250px;"> right:<div id="right" class="inner"></div> </div>
如果這是一個小專案,一個簡單的頁面,那這個實現這個需求是非常簡單的。但如果是一個比較複雜的專案,處理left和right都各有很多業務邏輯,需要把這兩部分放在不同模組裡,那麼這時候就不得不考慮一種新的解決方案。而且隨著時間的增加,可能在某天產品又會增加一個新模組,也要求A的值變化時作出相應反應。這時候,問題就變得越來越複雜了。
事件廣播和偵聽,是實現這種需求最好的方案。
那什麼是事件廣播和偵聽,我個人的理解是:當發生某一事件時,在某個頻道上發出廣播,向註冊到該頻道上的所有監聽者傳送資訊資料,讓各監聽者在自己內部做相應處理。
下面我們用事件廣播和偵聽來實現上面的需求,步驟如下:
1、建立一個頻道
//頻道說明,建立一個頻道時寫清楚
//說明這個頻道是幹什麼的;什麼時候會廣播這個頻道;廣播時傳的資料結構或引數形式,等等
Utils.Listener.createChannel('index.test.change'); //可以使用這種類似名稱空間的字串做頻道名,這裡表示index頁下的test發生改變
2、在不同程式碼模組裡向頻道index.test.change新增監聽者。這裡監聽者的名字是changeLeft,第三個引數是收到廣播訊息時應該做什麼處理,即回撥函式。
新增另外一個監聽者changeRight,當然在一個頻道上你可以新增很多很多的監聽者:Utils.Listener.add('index.test.change', 'changeLeft', function(data){ $E('left').innerHTML = data; //改變left的內容 //Utils.Listener.remove('index.test.change', 'changeLeft'); //移除頻道監聽 //Utils.Listener.remove('index.test.change'); //刪除整個頻道 });
Utils.Listener.add('index.test.change', 'changeRight', function(data){
$E('right').innerHTML = data;
});
3、給A框繫結keyup事件,讓它的值發生變化時發出廣播 Core.Events.addEvent($E('test'), function(){
//在頻道 index.test.change 發出廣播
Utils.Listener.broadcast('index.test.change', $E('test').value); //第二個引數為廣播時發出的資訊,你可以新增多個引數
//在頻道 index.test.change 向監聽者 changeLeft 發出單播
//Utils.Listener.unicast('index.test.change', 'changeLeft', $E('test').value+'. Wahaha~');
}, 'keyup');
大功告成!改變A框的值時,left和right已能跟隨改變內容。
當然,事件廣播和偵聽的功能遠遠不只這個,你可以用它來實現各相互獨立的業務模組之間的通訊,等等。還有,做web編輯器的時候,也需要用到,因為你可能要在iframe的body上多次監聽keyup之類的事件,如果每次都綁事件,那效率太低了,所以如果使用事件廣播的話,只需要在body上綁一個keyup事件,然後廣播這個事件就行了。
最後把廣播和偵聽的程式碼貼出來:
/**
* 事件偵聽、廣播、單播
* @method
* @example
//建立頻道“unameChange”
Utils.Listener.createChannel('unameChange');
//建立頻道“uname.change”---天然支援“偽”名稱空間!!!
Utils.Listener.createChannel('uname.change');
//新增unameChange的監聽者“updateTray”
Utils.Listener.add('unameChange', 'updateTray', function(){
//TODO
});
//新增unameChange的監聽者“changeFootbar”
Utils.Listener.add('unameChange', 'changeFootbar', function(){
//TODO
});
//發出廣播
Utils.Listener.broadcast('unameChange', someData);
//發出廣播
Utils.Listener.broadcast('uname.change', someData);
//發出單播
Utils.Listener.unicast('uname.change', 'changeFootbar', someData);
*/
var Utils = {};
!function(){
if(Utils.Listener){
return;
}
var _channels = {},
slice = Array.prototype.slice;
Utils.Listener = {
//channelName 頻道名,天然支援“偽”名稱空間。例如:uname.change
createChannel: function(channelName){
if( _channels[channelName] ){
traceError('Channel "'+channelName+'" has been defined!');
}else{
_channels[channelName] = {};
}
},
//channelName 監聽頻道
//listenerName 監聽者
//handler 發生廣播時的執行函式
add: function(channelName, listenerName, handler){
var channel = _channels[channelName];
if( !channel ){
traceError('Channel "'+channelName+'" has NOT been defined!');
return;
}
if( channel[listenerName] ){
traceError(channelName+':'+listenerName+'" has been defined!');
return;
}
channel[listenerName] = handler;
},
broadcast: function(channelName/*, data...*/){
var channel = _channels[channelName];
if( channel ){
for(var p in channel){
if( channel[p] ){
channel[p].apply(null, slice.call(arguments,1));
}
}
}
},
unicast: function(channelName, listenerName/*, data...*/){
var channel = _channels[channelName];
if( channel && channel[listenerName]){
channel[listenerName].apply(null, slice.call(arguments,2));
}
},
//channelName 頻道名
//listenerName 可選,如果沒有,將刪除整個頻道
remove: function(channelName, listenerName){
var channel = _channels[channelName];
if( channel ){
if(listenerName){
channel[listenerName] = null;
delete channel[listenerName];
}else{
channel = null;
delete _channels[channelName];
}
}
}
};
}();
歡迎大家提出改進建議。