发布-订阅模式
发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。JavaScript开发中我们一般用事件模型来代替传统的发布-订阅模式
现实中的发布-订阅模式
小明最近喜欢上吃老北京烧饼,可是到了卖烧饼的地方发现已经卖完了,而且排队的人还很多.幸运的是卖烧饼那个MM看小明长得帅,告诉小明等一会就有烧饼吃啦!可是小明现在还有约会要去,不知道烧饼能什么时候出锅,总不能因为吃烧饼而不去约会吧!这时候小明灵机一动,说烧饼MM把你电话给我吧!我先去忙,等会打电话问你烧饼好了没有。烧饼MM也没想太多,把电话给小明了。后来小龙也来买烧饼,情况跟小明差不多,小龙也把烧饼MM的电话要走了。可是问题就这来了,小明、小龙一会儿打一个电话给烧饼MM,导致烧饼MM很烦,辞职走了不干了。
通过上边的事情我们可以发现,存在好多问题
第一:卖烧饼的MM应该充当发布者
第二:小明小龙的电话应该保存在卖烧饼的用户列表中,如果卖烧饼的MM离职,这用户就会丢失
第三:实际上没有这么笨蛋的销售方式的
卖烧饼的店主可以把小明、小龙的电话记录下来,等店里有烧饼了在通知小龙小明来拿这就是所谓的发布-订阅模式,代码如下:
/*烧饼店*/ var Sesamecakeshop={ clienlist:[],//缓存列表 addlisten:function(fn){//增加订阅者 this.clienlist.push(fn); }, trigger:function(){//发布消息 for(var i=0,fn;fn=this.clienlist[i++];){ fn.apply(this,arguments); } } } /*小明发布订阅*/ Sesamecakeshop.addlisten(function(price,taste){ console.log("小明发布的"+price+"元,"+taste+"味道的"); }); /*小龙发布订阅*/ Sesamecakeshop.addlisten(function(price,taste){ console.log("小龙发布的"+price+"元,"+taste+"味道的"); }); Sesamecakeshop.trigger(10,"椒盐");
从代码中可以看出,只有小明,小龙预定了烧饼,烧饼店就可以发布消息告诉小龙与小明。但是有个问题不知道大家发现了没有。小明只喜欢椒盐味道的。而小龙只喜欢焦糖味道的。上面的代码就满足不了客户的需求,给客户一种感觉就是,不管我喜欢不喜欢,你都会发给我。如果发布比较多,客户就会感到厌烦,甚至会想删除订阅。下边是对代码进行改良大家可以看看。
/*烧饼店*/ var Sesamecakeshop={ clienlist:{},/*缓存列表*/ /** * 增加订阅者 * @key {String} 类型 * @fn {Function} 回掉函数 * */ addlisten:function(key,fn){ if(!this.clienlist[key]){ this.clienlist[key]=[]; } this.clienlist[key].push(fn); }, /** * 发布消息 * */ trigger:function(){ var key=[].shift.call(arguments),//取出消息类型 fns=this.clienlist[key];//取出该类型的对应的消息集合 if(!fns || fns.length===0){ return false; } for(var i=0,fn;fn=fns[i++];){ fn.apply(this,arguments); } }, /** * 删除订阅 * @key {String} 类型 * @fn {Function} 回掉函数 * */ remove:function(key,fn){ var fns=this.clienlist[key];//取出该类型的对应的消息集合 if(!fns){//如果对应的key没有订阅直接返回 return false; } if(!fn){//如果没有传入具体的回掉,则表示需要取消所有订阅 fns && (fns.length=0); }else{ for(var l=fns.length-1;l>=0;l--){//遍历回掉函数列表 if(fn===fns[l]){ fns.splice(l,1);//删除订阅者的回掉 } } } } } /*小明发布订阅*/ Sesamecakeshop.addlisten("焦糖",fn1=function(price,taste){ console.log("小明发布的"+price+"元,"+taste+"味道的"); }); /*小龙发布订阅*/ Sesamecakeshop.addlisten("椒盐",function(price,taste){ console.log("小龙发布的"+price+"元,"+taste+"味道的"); }); Sesamecakeshop.trigger("椒盐",10,"椒盐"); Sesamecakeshop.remove("焦糖",fn1);//注意这里是按照地址引用的。如果传入匿名函数则删除不了 Sesamecakeshop.trigger("焦糖",40,"焦糖");
删除的时候需要注意的是,如果订阅的时候传递的是匿名函数,删除的时候如果传入的也是匿名函数。则删除不了。因为删除时候是按照地址引用删除的。传进去的两个匿名函数,对应的地址引用是不同的。
web前端中使用到的发布-订阅模式
比如咱们常见的用户身份分别有不同的功能,超级管理员拥有最高权限,可以删除修改任意用户。而普通用户则只能修改自己的账户信息。首先是用户身份验证,验证通过之后对应功能才可以显示。
//登录发布-订阅模式 login={ clienlist:{},/*缓存列表*/ /** * 增加订阅者 * @key {String} 类型 * @fn {Function} 回掉函数 * */ addlisten:function(key,fn){ if(!this.clienlist[key]){ this.clienlist[key]=[]; } this.clienlist[key].push(fn); }, /** * 发布消息 * */ trigger:function(){ var key=[].shift.call(arguments),//取出消息类型 fns=this.clienlist[key];//取出该类型的对应的消息集合 if(!fns || fns.length===0){ return false; } for(var i=0,fn;fn=fns[i++];){ fn.apply(this,arguments); } } } //超级管理员修改所有用户 var editall=(function(){ login.addlisten("loginsucc",function(data){ editall.setview(data); }); return{ setview:function(data){ console.log(data); console.log("超级管理员修改所有用户"); } } })(); //仅仅修改自己 var editOwn=(function(){ login.addlisten("loginsucc",function(data){ editOwn.setview(data); }); return{ setview:function(data){ console.log(data); console.log("仅仅修改自己"); } } })();
发布-订阅模式简单封装
var _Event=(function(){ var clienlist={}, addlisten,trigger,remove; /** * 增加订阅者 * @key {String} 类型 * @fn {Function} 回掉函数 * */ addlisten=function(key,fn){ if(!clienlist[key]){ clienlist[key]=[]; } clienlist[key].push(fn); }; /** * 发布消息 * */ trigger=function(){ var key=[].shift.call(arguments),//取出消息类型 fns=clienlist[key];//取出该类型的对应的消息集合 if(!fns || fns.length===0){ return false; } for(var i=0,fn;fn=fns[i++];){ fn.apply(this,arguments); } }; /** * 删除订阅 * @key {String} 类型 * @fn {Function} 回掉函数 * */ remove=function(key,fn){ var fns=clienlist[key];//取出该类型的对应的消息集合 if(!fns){//如果对应的key没有订阅直接返回 return false; } if(!fn){//如果没有传入具体的回掉,则表示需要取消所有订阅 fns && (fns.length=0); }else{ for(var l=fns.length-1;l>=0;l--){//遍历回掉函数列表 if(fn===fns[l]){ fns.splice(l,1);//删除订阅者的回掉 } } } }; return{ addlisten:addlisten, trigger:trigger, remove:remove } })(); _Event.addlisten("jianbing",function(d,all){ console.log("发布的消息来自:"+d+",具体信息:"+all); }); _Event.addlisten("jianbing",function(d,all){ console.log("发布的消息来自:"+d+",具体信息:"+all); }) _Event.trigger("jianbing","小小坤","前端工程师,擅长JavaScript,喜欢结交更多的前端技术人员,欢迎喜欢技术的你加QQ群:198303871")
总结:
发布-订阅模式就是常说的观察者模式,在实际开发中非常有用。它的优点是为时间是解耦,为对象之间解构,它的应用非常广泛,既可以在异步编程中也可以帮助我们完成更松的解耦。发布-订阅模式还可以帮助我们实现设计模式,从架构上来看,无论MVC还是MVVC都少不了发布-订阅模式的参与。然而发布-订阅模式也存在一些缺点,创建订阅本身会消耗一定的时间与内存,也许当你订阅一个消息之后,之后可能就不会发生。发布-订阅模式虽然它弱化了对象与对象之间的关系,但是如果过度使用,对象与对象的必要联系就会被深埋,会导致程序难以跟踪与维护。