Skip to main content

发布订阅模式

前言#

很久前想深入理解Vue的底层原理,看了很多博客,只知道是“数据劫持+发布订阅模式”,之前写了一篇博客,懂了数据劫持,最近看到很多源码分析mvvm框架,说了很多次“发布订阅模式”,花了一下午加一晚上终于算是弄懂了!(前期的很多积累很重要,没有基础的支撑,感觉花再多时间啃某个知识点也是浪费时间,因为可能一个点不懂,一连串都会断掉,体会不到设计的巧妙)

正题#

本文将从以下四个方面介绍

  1. 发布订阅模式的作用是什么
  2. 发布订阅模式详解
  3. 实现发布订阅模式
  4. 发布订阅模式与观察者模式的区别

1. 发布订阅模式的作用是什么#

两个字——解耦
换一个比较高大上的说法:定义了一个一对多的依赖关系,当有关状态发生变更时,则执行相应的更新。
实际上,设计模式是为了更好的维护代码。在实现功能方面,把代码全都写在一起是完全没问题的,但是这样的代码一般称之为“屎山”。每更新一个需求,可能就在原来某个判断条件内写一大堆逻辑。极其不利于后期的维护。而发布订阅的作用,就是为了优雅的解决这种需求的,一种设计模式。

2. 发布订阅模式详解#

发布订阅模式需要哪几个步骤?

  1. 添加订阅
  2. 取消订阅
  3. 发布订阅

对于订阅,需要两个参数:一个是订阅的主题,一个是订阅内容发布后需要进行处理的操作。

设想这么一个场景:我要用洗衣机洗衣服,有白色的衣服和黑色的衣服,和一个盆。首先,把盆里白色的衣服放到洗衣机里,白色的衣服有外套,衬衫,将白衣服一件一件的放到洗衣机里,然后打开洗衣机。以此类推处理黑色的衣服。

对上述场景分析,带入到发布订阅模式中:

  • 主题: 白色衣服、黑色衣服
  • 操作: 洗衣服
  • 添加订阅: 将某个颜色的某件衣服放到盆里
  • 取消订阅: 将某个颜色的所有衣服从盆里取出来
  • 发布订阅: 将盆里的某种颜色的所有衣服,放到洗衣机里洗

3. 实现发布订阅模式#

话不多说,第二步的分析,直接上代码:

function subPub() {  this.list = {};   //这就是那个盆}
subPub.prototype = {  constructor: subPub,  subscribe(key, fn) {      //订阅    if(!this.list[key]) {   //如果之前没有这个类别,新增一个      this.list[key] = [];    }    this.list[key].push(fn);  //将事件触发后需要进行的操作放进去  },
  unsubscribe(key) {       //取消订阅    if(key in this.list) {      delete this.list[key];  //删除这个属性,即取消所有内容,将白色衣服全都取出来    }  },
  publish(key) {             //发布某个主题的订阅内容    if(!this.list[key]) {      return    }    var args = Array.prototype.slice.call(arguments, 1);  //将传入的参数取下来    var fns = this.list[key];   //将某个主题的全部内容取出    for(let i=0; i<fns.length; i++) {      fns[i].apply(this, args);   //this还是用当前调度中心的this,不然参数都传不过去    }  }
}
function fn1() {  console.log("我是白衬衫,我被洗了,啊啊啊");}function fn2() {  console.log("我是白裤子,我被洗了,啊啊啊");}function fn3() {  console.log("我是黑黑黑衬衫,我被洗了,啊啊啊");}
var sub = new subPub();   //新建发布订阅对象
sub.subscribe("wash_white", fn1);     //将白衬衫放到盆里sub.subscribe("wash_white", fn2);     //将白裤子放到盆里sub.subscribe("wash_black", fn3);     //将黑衬衫放到盆里
sub.publish("wash_white");    //发布订阅的内容,执行相应的操作。  打开洗衣机洗所有的白衣服//我是白衬衫,我被洗了,啊啊啊//我是白裤子,我被洗了,啊啊啊
sub.publish("wash_black");    //洗所有的黑衣服//我是黑黑黑衬衫,我被洗了,啊啊啊

//也可以取消订阅sub.unsubscribe("wash_white");   //白衬衫今天约会还要穿,先不洗了吧sub.publish("wash_white");    //洗所有的白衣服   什么都不会发生
sub.publish("wash_black");    //黑衣服没事//我是黑黑黑衬衫,我被洗了,啊啊啊

//也可以在发布主题的时候传参function fn4(args) {  console.log("这个参数是发布的时候传送过来的:--->"+ args);}
sub.subscribe("test_args", fn4);sub.publish("test_args", "我是从发布订阅内部传过来的参数!!");//这个参数是发布的时候传送过来的:--->我是从发布订阅内部传过来的参数!!

以上代码,在Chrome控制台亲测有效。

4. 发布订阅模式与观察者模式的区别#

看了很多说观察者模式和发布订阅模式是一样的,我觉得,其设计思想是一样的,实际上还是有些许差别。

先上观察者模式代码:

function Subject() {    //所观察的主题  this.observers = [];  //这里只是一个数组,只能有一个主题}
Subject.prototype = {  constructor: Subject,  add(ob) {     //添加观察者对象    this.observers.push(ob);  },
  remove(ob) {  //删除观察者对象    var obs = this.observers;    for(let i=0; i<obs.length; i++) {      if(ob===obs[i]) {        this.observers.splice(i, 1);        break;      }    }  },
  notify() {    //发布消息    // var args = Array.prototype.slice.call(arguments);    var obs = this.observers;    for(let i=0; i<obs.length; i++) {      obs[i].update();    }  }}

function Observer(name) {   //观察者对象  this.name = name;}
Observer.prototype = {  constructor: Observer,  update() {                //接受到notify后,更新的函数    console.log(this.name);  }}
var sub = new Subject();        //新建主题var ob1 = new Observer("lrq");  //新建观察者var ob2 = new Observer("lrq2");var ob3 = new Observer("lrq3");sub.add(ob1);       //主题添加观察者sub.add(ob2);sub.add(ob3);sub.notify();       //发送消息// lrq// lrq2// lrq3

//也可以取消监听setTimeout(()=>{  sub.remove(ob2);    //第二个观察者不看了  sub.notify();}, 3000);//lrq//lrq3

以上就是观察者模式的代码,说一下我对两种模式的理解:

  1. 发布订阅比观察者更灵活一点,发布订阅可以定义多个主题,而观察者就相当于只有一个主题
  2. 发布订阅的执行函数是自己随便写的,观察者模式的执行函数是要定义一个Observer类,在里面封装相应的update方法,创建每个实例时传入不同的参数。

等到写的bug更多的时候,看问题可能会更透彻吧,现在只是基本会用了这个设计模式。

以上。
written by LRQ
2019.12.5