先来看html页面的主要结构:
<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>
需求:id为test的输入框(以下简称A)值改变时,实时改变id为left和right的内容。
如果这是一个小项目,一个简单的页面,那这个实现这个需求是非常简单的。但如果是一个比较复杂的项目,处理left和right都各有很多业务逻辑,需要把这两部分放在不同模块里,那么这时候就不得不考虑一种新的解决方案。而且随着时间的增加,可能在某天产品又会增加一个新模块,也要求A的值变化时作出相应反应。这时候,问题就变得越来越复杂了。
事件广播和侦听,是实现这种需求最好的方案。
那什么是事件广播和侦听,我个人的理解是:当发生某一事件时,在某个频道上发出广播,向注册到该频道上的所有监听者发送信息数据,让各监听者在自己内部做相应处理。这样做可以解耦各模块,很方便地拆卸每个模块。
下面我们用事件广播和侦听来实现上面的需求,步骤如下:
1、创建一个频道
//频道说明,创建一个频道时写清楚
//说明这个频道是干什么的;什么时候会广播这个频道;广播时传的数据结构或参数形式,等等
Utils.Listener.createChannel('index.test.change'); //可以使用这种类似命名空间的字符串做频道名,这里表示index页下的test发生改变
2、在不同代码模块里向频道index.test.change添加监听者。这里监听者的名字是changeLeft,第三个参数是收到广播消息时应该做什么处理,即回调函数。
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'); //删除整个频道
});
添加另外一个监听者changeRight,当然在一个频道上你可以添加很多很多的监听者:
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];
}
}
}
};
}();
欢迎大家提出改进建议。