当前位置: 首页 > Web前端 > HTML5

JavaScript中的发布-订阅模式(观察者模式)

时间:2023-04-05 17:42:47 HTML5

的前身发布/订阅模式——观察者模式观察者模式定义了对象之间一对多的依赖关系。当一个对象的状态发生变化时,所有依赖它的对象都会自动得到通知和更新。观察者模式属于行为模式,行为模式关注的是对象之间的通信,而观察者模式就是观察者与被观察者之间的通信。观察者模式有一个别名,称为“发布-订阅模式”或“订阅-发布模式”。订阅者和订阅目标链接在一起。当订阅目标发生变化时,会一一通知订阅者。什么是发布/订阅模式其实24种基本设计模式中并没有发布-订阅模式。如上所述,它只是观察者模型的别称。但经过时间的沉淀,他似乎变强了,变成了独立于观察者模式的不同设计模式。在目前的发布-订阅模型中,称为发布者的消息发送者并不直接将消息发送给订阅者,这意味着发布者和订阅者不知道对方的存在。在发布者和订阅者之间有第三个组件,称为消息代理或调度中心或中间件,它维护发布者和订阅者之间的连接,过滤所有从发布者传入的消息并相应地分发给订阅者。观察者模式和发布订阅模式的区别在于定义了一对多的依赖关系,相关状态发生变化时进行相应的更新。不同的是,在Observer模式下,一系列依赖于Subject对象的Observer对象,在收到通知后只能执行同一个具体的update方法(没有通过key值的判断直接触发订阅的方法)middle),而在发布-订阅模式下,可以根据不同的主题(由中介控制)执行不同的自定义事件。相对来说,发布-订阅模型比观察者模型更灵活。发布-订阅模型的根本作用广泛用于异步编程(代替回调函数的传递)和对象之间的松耦合代码所以客户等于订阅者,订阅兔妈头。而且妈妈兔头有货的时候,你得通知客户去买,不然赚不到钱,还得通知所有订阅者,兔头有货了。这时候兔头妈妈的店就是发布者。/*兔子店*/varshop={listenList:[],//缓存列表addlisten:function(fn){//添加订阅者this.listenList.push(fn);},trigger:function(){//发布消息for(vari=0,fn;fn=this.listenList[i++];){fn.apply(this,arguments);}}}/*小明订阅了店铺*/shop.addlisten(function(taste){console.log("通知小明,"+taste+"味道不错");});/*小龙订阅了到店*/shop.addlisten(function(taste){console.log("通知小龙,"+taste+"味道不错");});/*小红订阅了店*/shop.addlisten(function(taste){console.log("通知小红,"+taste+"味道不错");});//发布订阅shop.trigger("mediumspicy");//控制台通知小明,中辣做好了,通知小龙,中辣好了,通知小红,中辣做好了上面的案例升级有一个问题,因为触发的时候,所有的订阅都会被触发,没有区分判断,所以需要一个Key来区分订阅的类型,根据不同的情况触发。订阅是可以取消的。升级思路:创建一个对象(缓存列表)addlisten方法,将订阅回调函数fn添加到缓存列表listenList中。trigger方法将arguments中的第一个作为key,根据key值执行相应缓存列表中的remove函数。方法可以根据key值取消订阅/*rabbitstore*/varshop={listenList:{},//缓存对象addlisten:function(key,fn){//如果没有key,给一个初始值给避免调用错误if(!this.listenList[key]){this.listenList[key]=[];}//添加订阅者,key是一个订阅类型this.listenList[key].push(fn);},trigger:function(){constkey=Array.from(arguments).shift()constfns=this.listenList[key]//这里排除了两种特殊情况,第一种是从未订阅过的触发类型,第二个If(!fns||fns.length===0){returnfalse;}//发布消息并触发所有相同类型的订阅,fns.forEach((fn)=>{fn.apply(this,arguments);})/*for(vari=0,fn;fn=fns[i++];){fn.apply(this,arguments);}*/},remove:function(key,fn){v??arfns=this.listenList[key];//获取该类型对应的消息集if(!fns){//如果对应的key没有被订阅,直接返回r返回假;}if(!fn){//如果没有传入具体回调,则表示需要取消所有订阅fns&&(fns.length=0);}else{for(varl=fns.length-1;l>=0;l--){//遍历返回函数列表if(fn===fns[l]){//这里比较的是传入地址,所以不能直接使用匿名函数fns.splice(l,1);//删除订阅者的回调}}}}}functionxiaoming(taste){console.log("通知小明,"+taste+"的味道不错");}functionxiaolong(taste){console.log("通知小龙,"+taste+"味道不错");}functionxiaohong(taste){console.log("通知小红,"+taste+"味道不错");}//小明订阅了店铺shop.addlisten('中辣',xiaoming);shop.addlisten('特辣',xiaoming);//小龙订阅了店铺shop.addlisten('微辣',xiaolong);//小红订阅了店铺shop.addlisten('mediumspicy',xiaohong);//小红突然不想吃shop.remove("mediumspicy",xiaohong);//中辣味准备好后,发布订阅shop.trigger("Spicy");shop.trigger("微辣");shop.trigger("extraspicy");关于发布-订阅顺序的讨论我们通常看到的是先订阅后发布,但是一定要遵守这个顺序吗?答案不一定。如果发布者先发布了一条消息,但是此时没有订阅者订阅这条消息,我们就可以防止这条消息消失在宇宙中。与QQ离线消息一样,离线消息保存在服务器中,收件人下次登录后会收到这条消息。同样,我们可以建立一个堆栈来存储离线事件。当一个事件发布时,如果此时没有订阅者订阅该事件,我们将发布事件的动作暂时包装在一个函数中,这些包装函数会被存放在栈中,当有对象时订阅事件,我们会遍历栈,依次执行这些包装函数,也就是重新发送里面的事件,但是离线事件的生命周期只有一次,就像qq未读消息只会提醒你一次一样.总结发布-订阅的优势是显而易见的。实现了时间上的解耦,对象之间的解耦。从架构上看,MVC和MVVM离不开发布-订阅的参与。同节点的EventEmitter也是发布订阅的