又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
通用发布-订阅模式的实现
// 提取发布-订阅功能,思想类似面向对象。面向对象写法对于提取同类功能确实有效。
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 false
if(!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)
},
小结
发布-订阅模式的优点:
- 时间上解耦
- 对象间解耦