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

JavaScript设计模式之发布-订阅模式(观察者模式)-Part2

时间:2023-04-02 21:34:04 HTML

《JavaScript设计模式与开发实践》读书笔记。阅读本文之前,推荐阅读JavaScript设计模式发布订阅模式(观察者模式)-Part1Part1中已经介绍了什么是发布订阅模式,同时发布订阅模式也有介绍得到实施。但是就Part1实现的例子来说,还是有两个小问题:我们给每个publisher对象都添加了listen和trigger方法,还有一个缓存列表clientList,这其实是一种资源浪费。订阅者和发布者之间存在一定的耦合。订阅者至少要知道发布者对象的名称,才能顺利订阅。如下://张三订阅问题A,sgQuestionSystem为发布者对象(问题系统)sgQuestionSystem.listen('questionA',3,function(questionTitle,content){console.log('张三,你之前订阅了问题:questionA');console.log('now'+questionTitle+'newnews');console.log('Contentis:'+content);});如果张三还对其他东西感兴趣,并且发布者不一样,那么这意味着张三需要订阅另一个发布者对象。如下://张三订阅文章A消息,sgArticleSystem为发布者对象(文章系统)sgArticleSystem.listen('articleA',3,function(articleTitle,content){console.log('张三你订阅了较早的文章:articleA');console.log('现在'+articleTitle+'新消息');console.log('内容为:'+content);});全球发布订阅对象相信大家都知道什么是中介公司?在现实生活中,我们只需要将订阅请求交给中介公司,各大出版商只需要通过中介公司发布信息即可。这样我们就不需要关心消息是哪个发布者发布的了,我们只需要关心消息是否能顺利接收。当然,订阅者和发布者都需要知道这个中介公司。同样,在程序中,我们可以使用一个全局的Event对象来实现中介公司的作用。事件充当消息中心,连接订阅者和发布者。看下面的代码:varEvent=(function(){varclientList={},//缓存列表listen,trigger,remove;listen=function(key,id,fn){if(!clientList[key]){clientList[key]=[];//初始化}clientList[key].push({//将订阅的id和回调函数添加到对应的消息列表id:id,fn:fn});};trigger=function(){varkey=Array.prototype.shift.call(arguments),fns=clientList[key];if(!fns||fns.length==0){returnfalse;}for(vari=0;i=0;l--){var_id=fns[l].id;如果(_id==id){fns.splice(l,1);//删除订阅者的回调函数}}}};return{listen:listen,trigger:trigger,remove:remove}})();Event.listen('questA',1,function(content){console.log('Contentis:'+content);});Event.trigger('questA','王舞回答了这个问题');发布前必须先订阅?我们知道的发布-订阅模型是订阅者必须先订阅一条消息,然后才能接收到发布者发布的消息。如果顺序颠倒,发布者先发布一条消息,在它之前没有订阅的对象,那么这条消息就消失在宇宙中。但是在实际项目中,存在先发布后订阅的情况。例如,在一个商城网站中,用户导航模块只有在获取到用户信息后才能渲染,而获取用户信息的操作是一个异步请求。一个事件只有在请求返回成功后才能发布,订阅了这个事件的用户导航模块才能收到这个信息。但是由于异步的原因,我们不能保证ajax请求的事件。可能用户的导航模块还没有加载(也就是消息还没有订阅),请求返回(也就是消息发布了)。因此,我们需要先让我们的发布订阅对象具备发布订阅的能力。具体实现请参考后续最终代码。全局事件的命名冲突。全局发布订阅对象中只有一个clientList,用于存放消息名称和回调函数。大家通过它订阅和发布各种消息。久而久之,难免会出现事件名称冲突,所以我们也可以给Event对象提供创建命名空间的能力。最终代码如下:varEvent=(function(){varglobal=this,Event,_default='default';Event=function(){var_listen,_trigger,_remove,_slice=Array.prototype.slice,_shift=Array.prototype.shift,_unshift=Array.prototype.unshift,namespaceCache={},_creat,find,each=function(ary,fn){v??arret;for(vari=0,l=ary.length;i=0;i--){if(cache[key][i]==fn){cache[key].splice(i,1);}}}}else{//若无fn,表示全部清空cache[key]=[];}}};_trigger=function(){varcache=_shift.call(arguments),key=_shift.call(arguments),args=arguments,_self=this,ret,stack=cache[key];如果(!堆栈||!堆栈长度){返回;}returneach(stack,function(){returnthis.apply(_self,args);})};_create=f函数(命名空间){var命名空间=命名空间||_默认;varcache={},offlineStack=[],ret={listen:function(key,fn,last){_listen(key,fn,cache);如果(offlineStack==null){返回;}if(last=='last'){offlineStack.length&& offlineStack.pop()();}else{each(offlineStack,function(){this();});}offlineStack=null;},一个:函数(key,fn,last){_remove(key,cache);this.listen(键,fn,最后);},移除:函数(键,fn){_remove(键,缓存,fn);},触发:function(){varfn,args,_self=this;_unshift.call(参数,缓存);args=参数;fn=function(){返回_trigger.apply(_self,args);};如果(offlineStack){返回offlineStack.push(fn);}返回fn();}};返回命名空间?(命名空间缓存[命名空间]?命名空间缓存[命名空间]:命名空间C疼痛[命名空间]=ret):ret;};return{create:_create,one:function(key,fn,last){varevent=this.create();event.one(key,fn,last);},移除:function(key,fn){v??arevent=this.create();event.one(键,fn);},listen:function(key,fn,last){varevent=this.create();event.listen(key,fn,last);},触发:function(){varevent=this.create();event.trigger.apply(this,arguments);}};}();returnEvent;})();/**********先发布后订阅************/Event.trigger('click',1);Event.listen('click',function(a){console.log(a)});/**********使用命名空间************/Event.create('namespace1').listen('点击',函数n(a){console.log(a);});Event.create('namespace1').trigger('click',1);附:JavaScript数据结构与算法系列:JS栈JS队列-优先队列、循环队列JavaScript设计模式系列:JavaScript设计模式之策略模式