又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
通用发布-订阅模式的实现
// 提取发布-订阅功能,思想类似面向对象。面向对象写法对于提取同类功能确实有效。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?.length){return false}for(var i=0,fn;fn=fns[i++];){fn.apply(this,arguments)}}}//可以给所有对象动态安装订阅发布功能var installEvent=function(obj){for(var key in event){obj[key]=event[key]}}
取消订阅事件
event.remove=function(key,fn){var fns=this.clientList[key]if(!fns) return falseif(!fn){ // 未传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) // 取消订阅函数}}}}
增加中介
在实现event后,并不需要对其他对象安装订阅发布功能,该 event对象自身可作为一个中介来管理订阅和发布。
var event={listen:()=>(),trigger:()=>(),remove:()=>(),}// 直接在event上触发事件,并设置事件监听,event就是这个中介
在使用全局对象event作为中介时,由于发布-订阅模式本身就有不关心发布者和订阅者的特性,如果创造了太多的全局event对象,不可避免的难以搞清楚模块与模块间的联系。
先发布再订阅
在很多业务场景中,发布行为会早于订阅行为,如设置了懒加载的模块订阅了全局消息,该模块只有在真正需要时才被加载进而产生订阅行为,但是先于订阅的一些发布信息仍然是必要的。
解决办法是建立一个存放离线事件的堆栈,
// 看样子是要改造发布函数var trigger=function(){var key=Array.prototype.shift.call(arguments),fns=this.clientList[key]if(!fns?.length){return false // 最初的发布函数在未找到对应的订阅者时返回false,也就是说原本模式下,晚于发布的订阅者将不会收到消息}for(var i=0,fn;fn=fns[i++];){fn.apply(this,arguments)}}// 改造的应该是对订阅函数的判断逻辑?会不会产生逻辑耦合的问题?var trigger2=function(){var key=Array.prototype.shift.call(arguments),fns=this.clientList[key]if(!fns?.length){offline.push(key) // 这是我自己瞎写的,把发布行为加入离线队列中return false}for(var i=0,fn;fn=fns[i++];){fn.apply(this,arguments)}}// 订阅函数也需要被改造一下,应该是在首次订阅的时候从离线堆栈中取出东西var listen=function(key, fn){if(!this.clientList[key]){this.clientList[key]=[]if(offline.has(key)){ // 这也是我瞎写的,如果还未有订阅队列,则检查离线队列中是否有发布行为,如果有则立即执行订阅回调fn()}}this.clientList[key].push(fn)},
小结
发布-订阅模式的优点:
- 时间上解耦
- 对象间解耦
