又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知

通用发布-订阅模式的实现

  1. // 提取发布-订阅功能,思想类似面向对象。面向对象写法对于提取同类功能确实有效。
  2. var event={
  3. clientList:[],
  4. listen:function(key, fn){
  5. if(!this.clientList[key]){
  6. this.clientList[key]=[]
  7. }
  8. this.clientList[key].push(fn)
  9. },
  10. trigger:function(){
  11. var key=Array.prototype.shift.call(arguments),
  12. fns=this.clientList[key]
  13. if(!fns?.length){
  14. return false
  15. }
  16. for(var i=0,fn;fn=fns[i++];){
  17. fn.apply(this,arguments)
  18. }
  19. }
  20. }
  21. //可以给所有对象动态安装订阅发布功能
  22. var installEvent=function(obj){
  23. for(var key in event){
  24. obj[key]=event[key]
  25. }
  26. }

取消订阅事件

  1. event.remove=function(key,fn){
  2. var fns=this.clientList[key]
  3. if(!fns) return false
  4. if(!fn){ // 未传fn则表示需要取消该key下的所有订阅
  5. fns&&fns.length=0
  6. }else{
  7. for(var l=fns.length-1;l>=0;l--){
  8. var _fn=fns[l]
  9. if(fn===_fn){
  10. fns.splice(l,1) // 取消订阅函数
  11. }
  12. }
  13. }
  14. }

增加中介

在实现event后,并不需要对其他对象安装订阅发布功能,该 event对象自身可作为一个中介来管理订阅和发布。

  1. var event={
  2. listen:()=>(),
  3. trigger:()=>(),
  4. remove:()=>(),
  5. }
  6. // 直接在event上触发事件,并设置事件监听,event就是这个中介

在使用全局对象event作为中介时,由于发布-订阅模式本身就有不关心发布者和订阅者的特性,如果创造了太多的全局event对象,不可避免的难以搞清楚模块与模块间的联系。

先发布再订阅

在很多业务场景中,发布行为会早于订阅行为,如设置了懒加载的模块订阅了全局消息,该模块只有在真正需要时才被加载进而产生订阅行为,但是先于订阅的一些发布信息仍然是必要的。
解决办法是建立一个存放离线事件的堆栈,

  1. // 看样子是要改造发布函数
  2. var trigger=function(){
  3. var key=Array.prototype.shift.call(arguments),
  4. fns=this.clientList[key]
  5. if(!fns?.length){
  6. return false // 最初的发布函数在未找到对应的订阅者时返回false,也就是说原本模式下,晚于发布的订阅者将不会收到消息
  7. }
  8. for(var i=0,fn;fn=fns[i++];){
  9. fn.apply(this,arguments)
  10. }
  11. }
  12. // 改造的应该是对订阅函数的判断逻辑?会不会产生逻辑耦合的问题?
  13. var trigger2=function(){
  14. var key=Array.prototype.shift.call(arguments),
  15. fns=this.clientList[key]
  16. if(!fns?.length){
  17. offline.push(key) // 这是我自己瞎写的,把发布行为加入离线队列中
  18. return false
  19. }
  20. for(var i=0,fn;fn=fns[i++];){
  21. fn.apply(this,arguments)
  22. }
  23. }
  24. // 订阅函数也需要被改造一下,应该是在首次订阅的时候从离线堆栈中取出东西
  25. var listen=function(key, fn){
  26. if(!this.clientList[key]){
  27. this.clientList[key]=[]
  28. if(offline.has(key)){ // 这也是我瞎写的,如果还未有订阅队列,则检查离线队列中是否有发布行为,如果有则立即执行订阅回调
  29. fn()
  30. }
  31. }
  32. this.clientList[key].push(fn)
  33. },

作者自己实现了先发布再订阅的模式,用的时候可以去查一下。

小结

发布-订阅模式的优点:

  1. 时间上解耦
  2. 对象间解耦