rabbitmq发布订阅模式_聊聊发布订阅设计模式

如果你学过vue,我想你一定对vue的父子组件通信产生过好奇,为什么子组件中$emit函数中 填写event:事件名,args:传递的参数,在父组件中写上对应的event事件名,和回调函数,就可以在回调函数中接收到子组件传递的args参数呢?我也很好奇,所以我就去百度同时看了vue2的源码,发现里面使用了一种设计模式,叫发布-订阅模式。于是,我就详详细细的去了解了发布-订阅模式是怎么回事。下面我也用我的方式来描述一下,我对发布订阅模式来的理解,以及我遇到的一些问题和如何解决这些问题。 发布-订阅模式,顾名思义,就是发布者发布消息,订阅者就可以用接收到发布者发布的信息,很好理解。转化成代码,要怎么写呢?看下面示例:
let eventsMixin = {      /* 收集订阅者 */      events: {},      /* 参数:订阅的主题。回调函数, 收集依赖 */      on(event, fn) {        (this.events[event] || (this.events[event] = [])).push(fn);      },      /* 发布数据 */      emit(event, content) {        if (!this.events[event] || this.events[event].length === 0) {          console.log('没有人订阅信息--->', event, content);          return;        }        let tempList = this.events[event];        tempList.forEach(fn => {           fn(content)        })      }    }    eventsMixin.on('handleTitle', handleTitle);    eventsMixin.emit('handleTitle', '标题');     /* 处理事件 */    function handleTitle(title) {      console.log('收到消息了--->', title);    }    
首先定义了一个eventsMixin对象,其中有一个events空对象,两个函数,on函数接收一个事件名,一个回调函数。emit函数接收一个事件名,一个需要发送的消息参数。on函数中把传递进来的fn函数,push到events对象的event属性中,看第六行代码,没有该变量先创建否则会报错(可想而知)。emit函数中去遍历events对象的event属性中的数据,并执行数据中的方法,因为在on函数中的event属性存的是个数组函数,此时emit函数中正好传递进来的数据,那么这个参数也正好可以被此前的fn函数中的参数接收,这样就完成了,订阅者订阅到了发布者发布的消息了。看结果: 44d4a25fc9cce03aff7c67d7b7741c19.png我自己打出这简单的几行代码以后,震惊到了,就这么几行代码,就很巧妙的实现了功能,对于数组中存储方法,在特定的时间再去执行该方法有了更加深刻的理解。下面在试试不同的事件,多个相同的事件,不同的处理,会不会收到数据
eventsMixin.on('handleTitle', handleTitle);eventsMixin.on('content', handleContent);eventsMixin.on('content', handleContent1);eventsMixin.emit('handleTitle', '标题');eventsMixin.emit('content', '内容'); function handleTitle(title) {  console.log('收到消息了--->', title);}function handleContent(content) {  console.log('收到消息了--->', content);}function handleContent1(content) {  console.log('收到消息了1--->', content);}

8b8a7e39a94031e4b5888655dfc13f06.png

答案是肯定的。至此我们对vue的子向 父传值的原理,和发布-订阅模式应该有了了解了。那么对定的off ,once 方法我们是不是也顺便了解了解呢?off的话就是,删除所有订阅者,也可以删除对应发布者的所有订阅者,还可以是对应发布者中某个订阅者,对应代码就是删除events对象中特定的数据。once的话和on很相似,只是发布者发布的消息只会接收到一次,仅此而已,对应代码就是先订阅到数据后,再删除。
// 取消订阅off 函数在eventsMixin对象中,简写了off(event, fn) {  /* 需要校验参数 */  let _this = this;  // let fns = _this.events[event];  /* 没有参数,移除所有的事件监听器 */  if (!arguments.length) {    _this.events = {};    return;  }  /* 只有事件名称,移除该事件所有的监听器 */  if (arguments.length === 1) {    delete _this.events[event];    return;  }  /* 如果同时提供了事件与回调,则只移除这个回调的监听器 */  if (arguments.length === 2) {    let fnList = _this.events[event];    if (!fnList) return;    // 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可    for (let i = 0; i < fnList.length; i++) {      if (fnList[i] === fn) {              fnList.splice(i, 1);        break      }    }  }}eventsMixin.on('handleTitle', handleTitle);eventsMixin.on('content', handleContent);eventsMixin.on('content', handleContent1);eventsMixin.emit('handleTitle', '标题');eventsMixin.emit('content', '内容');eventsMixin.off('content', handleContent);eventsMixin.emit('content', '新的内容');
看结果:

b11fbab1a50505c4578d8a284e3ea341.png

'content', handleContent 对应订阅者,被删除了。所以发布者再次发布信息的时候,只有'content', handleContent1对应的订阅者才能收到数据。看off函数,参数:一个事件名,一个函数。首先,如果没有参数,就删除所有的订阅者,如果只有一个参数,就删除对应的事件的所有订阅者,如果两个参数,就删除对应事件的对应订阅者。我想上面的代码应该很好理解的。下面再来介绍once方法,只触发一次。
/* 监听一次  函数在eventsMixin对象中,简写了*/once(event, fn) {  let _this = this;  // 先绑定,调用后删除  //    console.log(fn);  function on() {    _this.off(event, on);    fn.apply(_this, arguments);  }  on.fn = fn;  _this.on(event, on);},
eventsMixin.once('content', handleContent);eventsMixin.emit('content', '内容');eventsMixin.emit('content', '新的内容');

f5357fd5dbb1f7032f9a07b0a0752e3e.png

once函数里面还定义了一个on函数(姑且叫内部on函数),内部on函数中执行off方法,和fn函数(姑且叫内部fn函数)。内部fn函数的绑定了once方法中的形参fn。最后执行on函数(外部的on函数)。这里超级绕,有一个办法可以理清楚,那就是打断点调试,就可以看到函数的执行过程了。

最终结果也符合我们的预期。

难道到这里就结束了吗?并没有,下面我想分享的才是我遇到的坑和网上分享不同的地方。拿起小本本仔细听。

当我们再次订阅信息的时候

eventsMixin.once('content', handleContent);eventsMixin.once('content', handleContent1);eventsMixin.emit('content', '内容');

fb96cfbcff1222d832b3c8d197da521c.png

发现只打印出来一条信息了,按道理应该打印出来两条信息才对,因为我分别订阅了两次内容。

我看了很多遍代码后,也没有看出什么毛病,所以我就试着打断点去调试,发现当我们去做删除的时候eventList数组发生了变化,我们再在emit函数中进行forEach循环,那么就得不到数组中第二个函数了,它现在变成第一个了。那要怎么解决,好办,反向遍历即可。

let i = eventList.length;while (i--) {  eventList[i](content);}

f0a5b263678e950bd605823dbb4089fe.png

得到数据了。所以遍历的时候去删除数组中的数据就要注意了,这一点,我在java遍历数组并删除数据遇到的问题也做过相应的笔记。当时使用的是迭代器。es6中也引入了迭代器的概念,那么js的迭代器也可以实现(不过我还没试,你可以试试)。

集合与泛型第三篇

在vue2源码中也能看到这样的处理(我当然是抄袭的了102802134d5083292e615364c8926f7c.png)

Vue.prototype.$off = function (event, fn) {      var vm = this;      // all      if (!arguments.length) {        vm._events = Object.create(null);        return vm      }      // array of events      if (Array.isArray(event)) {        for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {          vm.$off(event[i$1], fn);        }        return vm      }      // specific event      var cbs = vm._events[event];      if (!cbs) {        return vm      }      if (!fn) {        vm._events[event] = null;        return vm      }      // specific handler      var cb;      var i = cbs.length;      while (i--) {        cb = cbs[i];        if (cb === fn || cb.fn === fn) {          cbs.splice(i, 1);          break        }      }      return vm    };

只不过源码里面是写在off函数里的。原理还是一样倒过来遍历数组

下面是完整的代码

<html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Documenttitle>head><body>  <script>    /* 定义调度中心 */    let eventsMixin = {      /* 收集订阅的主题 */      events: {},      /* 参数:订阅的主题。回调函数, 收集依赖 */      on(event, fn) {        (this.events[event] || (this.events[event] = [])).push(fn);      },      /* 发布数据 */      emit(event, content) {        if (!this.events[event] || this.events[event].length === 0) {          console.log('没有人订阅信息--->', event, content);          return;        }        let eventList = this.events[event];        // eventList.forEach(fn => {        //   fn(content)        // })        // for (let i in eventList) {        //     eventList[i](content);        // }        // for (let i = 0; i < eventList.length; i++) {        //    eventList[i](content);        // }        let i = eventList.length;        while (i--) {          eventList[i](content);        }        // for (let item of eventList) {        //   item(content);        // }      },      /* 监听一次 */      once(event, fn) {        let _this = this;        // 先绑定,调用后删除        //    console.log(fn);        function on() {          // console.log(fn);          _this.off(event, on);          fn.apply(_this, arguments);        }        on.fn = fn;        _this.on(event, on);      },      // 取消订阅      off(event, fn) {        /* 需要校验参数 */        let _this = this;        // let fns = _this.events[event];        /* 没有参数,移除所有的事件监听器 */        if (!arguments.length) {          _this.events = {};          return;        }        /* 只有事件名称,移除该事件所有的监听器 */        if (arguments.length === 1) {          delete _this.events[event];          return;        }        /* 如果同时提供了事件与回调,则只移除这个回调的监听器 */        if (arguments.length === 2) {          let fnList = _this.events[event];          if (!fnList) return;          // 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可          for (let i = 0; i < fnList.length; i++) {            if (fnList[i] === fn || fnList[i].fn === fn) {              fnList.splice(i, 1);              // console.log(fnList);              break            }          }          // let i = fnList.length          // while (i--) {          //     cb = fnList[i];          //     if (cb === fn || cb.fn === fn) {          //         fnList.splice(i, 1);          //         break          //     }          // }        }      }    }    /* 订阅者 到订阅调度中心,调度想要的内容(即主题) */    // eventsMixin.once('handleTitle', handleTitle);    eventsMixin.once('content', handleContent);    eventsMixin.once('content', handleContent1);    // eventsMixin.once('content', handleContent2);    // eventsMixin.once('content', handleTitle5);    /* 发布者 向调度中心发布内容,(要设置主题,目的,只有订阅了该主题的用户才可以获取到相应的数据)  */    // eventsMixin.emit('handleTitle', '标题');    // eventsMixin.emit('content', '内容');    eventsMixin.emit('content', '内容');    // eventsMixin.off('content', handleContent);    // eventsMixin.off();    // eventsMixin.emit('content', '新的内容');    // eventsMixin.on('content', handleTitle);    // eventsMixin.emit('content', '内容');    // eventsMixin.off();    /* 处理事件 */    function handleTitle(title) {      console.log('收到消息了--->', title);    }    function handleContent(content) {      console.log('收到消息了--->', content);    }    function handleContent1(content) {      console.log('收到消息了1--->', content);    }    function handleContent2(content) {      console.log('收到消息了2--->', content);    }    function handleTitle5(title) {      console.log('收到消息了55--->', title);    }script>body>html>
总结:发布者与订阅者 通过event事件名来建立关系,订阅者通过回调函数接收发布者发布的消息

9369075fe48e67a6973d76c201a09e21.png

长按关注,听wzg扯淡

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值