发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
发布-订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。
可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。
自定义事件
- 首先要指定好谁充当发布者;
- 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者;
- 最后发布消息时,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数。
另外,我们还可以往回调函数里填入一些参数,订阅者可以接收这些参数。
最简单的发布-订阅模式:
var salesOffices = {}; //定义售楼处 salesOffices.clientList = []; //缓存列表,存放订阅者的回调函数 salesOffices.listen = function(fn){ //增加订阅者 this.clientList.push(fn); //订阅的消息添加进缓存列表 }; salesOffices.trigger = function(){ //发布模式 for(var i=0,fn;fn = this.clientList[i++]; ){ fn.apply(this,arguments); //arguments是发布消息时带上的参数 } }; salesOffices.listen(function(price,squareMeter){//小明订阅消息 console.log(‘价格=‘+price); console.log(‘squareMeter=‘+squareMeter); }); salesOffices.listen(function(price,squareMeter){//小红订阅消息 console.log(‘价格=‘+price); console.log(‘squareMeter=‘+squareMeter); }); salesOffices.trigger(2000000,88);//输出:200万,88平方米 salesOffices.trigger(3000000,110);//输出:300万,110平方米
让订阅者只订阅自己感兴趣的消息:
var salesOffices = {}; //定义售楼处 salesOffices.clientList = []; //缓存列表,存放订阅者的回调函数 salesOffices.listen = function(key,fn){ if(!this.clientList[key]){ //如果还没有订阅过此类消息,给该类消息创建一个缓存列表 this.clientList[key] = []; } this.clientList.push(fn); //订阅的消息添加进缓存列表 }; salesOffices.trigger = function(){ //发布消息 var key = Array.prototype.shift.call(arguments), //取出消息类型 fns = this.clientList[key]; //取出该消息对应的回调函数集合 if(!fns || fns.length ===0 ){ //如果没有订阅该消息,则返回 return false; }; for(var i=0,fn;fn = this.clientList[i++]; ){ fn.apply(this,arguments); //arguments是发布消息时附送的参数 } }; salesOffices.listen(‘squareMeter88‘,function(price){//小明订阅88的消息 console.log(‘价格=‘+price); }); salesOffices.listen(‘squareMeter110‘,function(price){//小红订阅110的消息 console.log(‘价格=‘+price); }); salesOffices.trigger(‘squareMeter88‘,88); salesOffices.trigger(‘squareMeter110‘,110);
通用实现:
//发布-订阅功能 var event = { clientList:[], listen: function(key,fn){ if(!this.clientList[key]){ this.clientList[key] = []; } this.clientList[key].push(fn); //订阅的消息添加进缓存列表 }, trigger: function(){ var key = Array.prototype.shift.call(arguments), fns = this.clientList[key]; if(!fns || fns.length ===0 ){ //如果没有绑定对应的消息 return false; } for(var i =0 ,fn;fn = fns[i++]; ){ fn.apply(this.arguments); //arguments是trigger时带上的参数 } } }; //给所有的对象都动态安装发布-订阅功能 var installEvent = function(obj){ for(var i in event){ obj[i] = event[i]; } }; var salesOffices = {}; installEvent(salesOffices); salesOffices.listen(‘squareMeter88‘,function(price){ //小明订阅消息 console.log(‘价格=‘+price); }); salesOffices.listen(‘squareMeter110‘,function(price){ //小红订阅消息 console.log(‘价格=‘+price); }); salesOffices.trigger(‘squareMeter88‘,2000000); salesOffices.trigger(‘squareMeter110‘,3000000);
取消订阅的事件:
//取消订阅的事件 event.remove = function(key,fn){ var fns = this.clientList[key] if(!fns){ //如果key对应的消息没有被人订阅,则直接返回 return false; } if(!fn){ //如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅 fns&&(fns.length = 0); }else{ for(var l = fns.length-1;l>=0;l--){ //反向遍历订阅的回调函数列表 var _fn = fns[l]; if(_fn === fn){ fns.splice(l,1);//删除订阅者的回调函数 } } } }; var salesOffices = {}; var installEvent = function(obj){ for(var i in event){ obj[i] = event[i]; } } installEvent(salesOffices); salesOffices.listen(‘squareMeter88‘,fn1=function(price){ //小明订阅消息 console.log(‘价格=‘+price); }); salesOffices.listen(‘squareMeter110‘,fn2=function(price){ //小红订阅消息 console.log(‘价格=‘+price); }); salesOffices.remove(‘squareMeter88‘,fn1); salesOffices.trigger(‘squareMeter110‘,2000000);
全局的发布-订阅模式:
发布-订阅模式可以用一个全局的Event对象来实现,订阅者不需要了解清楚来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event作为一个类似“中介者”的角色,把订阅者和发布者联系起来。
var Event = (function(){ var clientList = {}, listen, trigger, remove; listen = function(key,fn){ if(!clientList[key]){ clientList[key] = []; } clientList[key].push(fn); }; trigger = function(){ var key = Array.prototype.shift.call(arguments), fns = clientList[key]; if(!fns||fns.length===0){ return false; } for(var i=0,fn;fn=fns[i++]; ){ fn.apply(this,arguments); } }; remove = function(key,fn){ var fns = clientList[key]; if(!fns){ return false; } if(!fn){ fns&&(fns.length = 0); }else{ for(var l=fns.length-1;l>=0;l--){ var _fn = fns[l]; if(_fn ===fn){ fns.splice(l,1); } } } }; return { listen:listen, trigger:trigger, remove:remove } })(); Event.listen(‘squareMeter88‘,function(price){ console.log(‘价格=‘+price); }); Event.trigger(‘squareMeter88‘,2000000);
模块间通信:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>模块间通信</title> </head> <body> <button id="count">点我</button> <div id="show"></div> </body> </html> <script> var a = (function(){ var count = 0; var button = document.getElementById(‘count‘); button.click = function(){ Event.trigger(‘add‘,count++); } })(); var b = (function(){ var div = document.getElementById(‘show‘); Event.listen(‘add‘,function(count){ div.innerHTML = count; }); })(); </script>
必须先订阅再发布吗?
在某些情况下,我们需要先将这条消息保存下来,等到有对象来订阅它的时候,再重新把消息发布给订阅者。
全局事件的命名冲突:
给Event对象提供创建命名空间的功能。
在JavaScript中,我们用注册回调函数的形式来代替传统的发布-订阅模式,显得更加优雅和简单。
发布-订阅模式的优点非常明显,一位时间上的解耦,二为对象上的解耦。
时间: 2024-12-26 14:30:09