js经典设计模式--发布订阅模式

简介: 笔记

什么是发布-订阅模式



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

举个例子,售楼处卖房,那么售楼处要发布房型信息,那么它是发布者,中介关注房型,所以中介是订阅者,当售楼处发布消息之后或者房型信息更新之后,中介就会收到消息。紧接着他去通知客户。这么做的好处是:客户不用关心房型,不用和任何一家售楼处保持紧密的联系,只需要与某个中介联系,但是他可以通过中介知道所有房型的变化。所有客户与售楼处是没有耦合关系的。

如下图:

1.png同样的,我们可以把它运用到编程上面来,降低代码的耦合性。

我们试着使用发布订阅模式实现以上功能。

  let houseObj = {}; //定义发布者
  houseObj.list = []; //缓存列表 (花名册) 存放订阅者回调函数
  //增加订阅者
  houseObj.listen = function(fn){
  //订阅消息添加到缓存列表
     this.list.push(fn); 
   }
  //发布消息 是不是要遍历这个列表
   houseObj.trigger = function(){
    for(let i = 0,fn; fn = this.list[i++];){
            fn.apply(this,arguments); // arguments 是发布消息时附送的参数
        }
   }
  //小红的要求 (订阅)
  houseObj.listen(function(size){
    console.log('小红:我要的房子是'+size+'平米');
  })
  //小绿的要求 (订阅)
   houseObj.listen(function(size){
     console.log('小绿:我要的房子是'+size+'平米');
   })
  //执行
  houseObj.trigger(100);
  houseObj.trigger(150);


存在的问题



  • 写完这段代码之后,运行一下,会发现它打印了四次,而我们理想结果是2次,因为houseObj把所有的消息都发给某一个订阅者了。
  • 这样对于用户来说是十分不友好的,他只想看他订阅的那条消息。那我们优化一下。
let houseObj = {}; //发布者
houseObj.list = {}; //缓存列表 (花名册) 存放订阅者回调函数
//增加订阅者
houseObj.listen = function(key,fn){   //增加一个唯一标识key
  //如果没有订阅过此消息 给该消息创建一个缓存列表
    (this.list[key] || (this.list[key] = [])).push(fn)
}
//发布消息 遍历列表
houseObj.trigger = function(){
  //取出消息类型名称
  let key = Array.prototype.shift.call(arguments);
  // 取出该消息对应的回调函数的集合
  let fns = this.list[key];
  if(!fns || fns.length === 0){
    return;
  }
  for(let i = 0,fn; fn = fns[i++];){
          fn.apply(this,arguments); // arguments 是发布消息时附送的参数
      }
}
//小红的要求 (订阅)
houseObj.listen('big',function(size){
  console.log('小红:我要的房子是'+size+'平米');
})
 //小绿的要求 (订阅)
 houseObj.listen('small',function(size){
  console.log('小绿:我要的房子是'+size+'平米');
})
//执行
houseObj.trigger('big',100);
houseObj.trigger('small',150);

这样我们就会看到控制台只打印了两次,传了一个唯一的key。

我们知道,对于上面的代码,对于不同的用户去买房子这么一个对象houseObj 进行订阅,但是如果以后我们需要对买水果子买鞋子或者其他的对象进行订阅呢,我们如果每次都这么去写是不是会很麻烦?

我们需要复制上面的代码,再重新改下里面的对象代码;这样是很麻烦的。为此我们需要进行代码封装 :

// 定义一个对象
 let event = {
    list:{},
    listen: function(key,fn){   //增加一个唯一标识key
      //如果没有订阅过此消息 给该消息创建一个缓存列表
        (this.list[key] || (this.list[key] = [])).push(fn)
    },
   trigger: function(){
      //取出消息类型名称
      let key = Array.prototype.shift.call(arguments);
      let fns = this.list[key];
      if(!fns || fns.length === 0){
        return;
      }
      for(let i = 0,fn; fn = fns[i++];){
        fn.apply(this,arguments); // arguments 是发布消息时附送的参数
      }
    }
  }
  //定义一个initEvent函数,这个函数使所有的普通对象都具有发布订阅功能
  let initEvent = function(obj){
      for(let i in event){
        obj[i] = event[i];
      }
  };
  let houseObj = {};   //发布者对象
  initEvent(houseObj); //为对象添加发布-订阅功能     
  //小明订阅的消息
  houseObj.listen('big',function(size){
    console.log('小明:我要的房子是'+size+'平米');
  })
   //小绿订阅的消息
   houseObj.listen('small',function(size){
    console.log('小绿:我要的房子是'+size+'平米');
  })
  houseObj.trigger('big',100);
  houseObj.trigger('small',150);

如上,我们只需要调用initEvent,便可以使所有对象都拥有发布订阅模式。

那么,接下来,如果某用户不想订阅了


如何取消订阅?



//删除订阅
  event.remove = function(key,fn){
     let fns = this.list[key];
     //如果没有定阅过 直接返回false
     if(!fns){
       return false;
     }
     // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
     if(!fn){
       fn && (fns.length = 0);
     }else{
       for(let i = fns.length - 1;i >= 0; i-- ){
         let _fn = fns[i];
         _fn === fn && (fns.splice(i,1));  //删除订阅者对应的回调函数
       }
     }
   }
   //其余保持不变
   //小明订阅的第一条消息
   houseObj.listen('big',fn1 = function(size){
    console.log('小明:我要的第一套房子是'+size+'平米');
  })
   //小明订阅的第二条消息
   houseObj.listen('big',fn2 = function(size){
    console.log('小明:我要的第二套房子是'+size+'平米');
  })
  //删除第二条
  houseObj.remove('big',fn2);
  houseObj.trigger('big',100);

这样,控制台就会只有一条打印了,只有第一条消息还在。


继续深度解耦

1.我们给每个发布者对象都添加了 listen 和 trigger 方法,以及一个缓存列表 list,这其实是一种资源浪费。

2.小明跟售楼处对象还是存在一定的耦合性,小明至少要知道售楼处对象的名字是houseObj ,要知道房型是big,还是small或是normal才能顺利的订阅到事件。

所以我们继续优化,封装一个全局发布-订阅模式对象


封装全局发布-订阅模式对象


  let Event = (function(){
    let list = {},
        listen,
        trigger,
        remove;
        listen = function(key,fn){
          (list[key] || (list[key] = [])).push(fn);
        };
        trigger = function(){
          let key = Array.prototype.shift.call(arguments),
          // 取出该消息对应的回调函数的集合
              fns = list[key];
          if(!fns || fns.length === 0){
            return false;
          }
          for(let i = 0,fn; fn = fns[i++];){
            fn.apply(this,arguments); // arguments 是发布消息时附送的参数
          }
        };
        remove = function(key,fn){
          let fns = list[key];
          //如果没有定阅过 直接返回false
          if(!fns){
            return false;
          }
          // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
          // 小明在售楼处不买了 取消了 说要买三室一厅,四室一厅,结果都是吹牛皮
          if(!fn){
            fn && (fns.length = 0);
          }else{
            for(let i = fns.length - 1;i >= 0; i-- ){
              let _fn = fns[i];
              _fn === fn && (fns.splice(i,1));  //删除订阅者对应的回调函数
            }
          }
        };
        return {
          listen:listen,
          trigger:trigger,
          remove:remove
        }
  })();
  Event.listen('big',function(size){
    console.log('小明想要的房型大小是'+size+'平米');
  })
  Event.trigger('big',100);

这样,用户连售楼处是哪都不用管了,高度解耦


FAQ



1.创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发布—订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个 bug 不是件轻松的事情。

2.不要滥用





目录
相关文章
|
12天前
|
设计模式 前端开发 JavaScript
【JavaScript 技术专栏】JavaScript 设计模式与实战应用
【4月更文挑战第30天】本文探讨JavaScript设计模式在提升开发效率和代码质量中的关键作用。涵盖单例、工厂、观察者、装饰器和策略模式,并通过实例阐述其在全局状态管理、复杂对象创建、实时数据更新、功能扩展和算法切换的应用。理解并运用这些模式能帮助开发者应对复杂项目,提升前端开发能力。
|
1月前
|
设计模式 SQL 算法
设计模式了解哪些,模版模式
设计模式了解哪些,模版模式
22 0
|
1月前
|
设计模式 Java 数据库
小谈设计模式(2)—简单工厂模式
小谈设计模式(2)—简单工厂模式
|
11天前
|
设计模式 前端开发 Java
19:Web开发模式与MVC设计模式-Java Web
19:Web开发模式与MVC设计模式-Java Web
21 4
|
12天前
|
JavaScript 前端开发 IDE
【JavaScript与TypeScript技术专栏】JavaScript与TypeScript混合编程模式探讨
【4月更文挑战第30天】本文探讨了在前端开发中JavaScript与TypeScript的混合编程模式。TypeScript作为JavaScript的超集,提供静态类型检查等增强功能,但完全切换往往不现实。混合模式允许逐步迁移,保持项目稳定性,同时利用TypeScript的优点。通过文件扩展名约定、类型声明文件和逐步迁移策略,团队可以有效结合两者。团队协作与沟通在此模式下至关重要,确保代码质量与项目维护性。
|
15天前
|
设计模式 消息中间件 Java
Java 设计模式:探索发布-订阅模式的原理与应用
【4月更文挑战第27天】发布-订阅模式是一种消息传递范式,被广泛用于构建松散耦合的系统。在 Java 中,这种模式允许多个对象监听和响应感兴趣的事件。
35 2
|
19天前
|
设计模式 存储 JavaScript
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
|
19天前
|
设计模式 Java Go
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
|
21天前
|
设计模式
设计模式(一)简单工厂模式
设计模式(一)简单工厂模式
14 0
|
21天前
|
JavaScript 前端开发 索引
JavaScript中的正则表达式:使用与模式匹配
【4月更文挑战第22天】本文介绍了JavaScript中的正则表达式及其模式匹配,包括字面量和构造函数定义方式,以及`test()`、`match()`、`search()`和`replace()`等匹配方法。正则表达式由元字符(如`.`、`*`、`[]`)和标志(如`g`、`i`)组成,用于定义搜索模式。文中还分享了正则使用的技巧,如模式分解、非捕获分组和注释。掌握正则表达式能提升文本处理的效率和代码质量。