mitt用100行实现发布-订阅模式还收获了9k的Star

发布于:2023-06-06 ⋅ 阅读:(283) ⋅ 点赞:(0)

我们先了解什么是发布-订阅模式,发布-订阅模式它是一种对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到状态的通知。

发布-订阅模式流程如下:

  • 订阅者将自己想订阅的事件注册调度中心
  • 发布者发布该事件到调度中心时,调度中心执行订阅者注册的事件。

举一个生活中的例子:

张三最近看上一套房子,到售楼处才被告知,该楼盘已售罄。好在售楼MM告诉张三,不久后将有一些尾盘推出,开发商正在办理相关手续,手续办好后便可以购买,但到底什么时候好,目前还没有人知道。

于是张三记下了售楼处的电话,以后每天都会打电话过去询问是不是已经到了购买时间。除了张三,还有李四、王五、小明也会每天向售楼处咨询这个问题。一个星期过后,售楼MM决定辞职,因为厌倦了每天回答1000个相同内容的电话。

当然现实中没有这么笨的销售公司,实际是这样的:张三离开之前,将电话号码留在售楼处。售楼MM答应他,新楼盘一推出就马上发信息通知张三。李四、王五、小明也是一样的,他们的电话号码记录在售楼处的花名册上,新楼盘推出时,售楼MM翻开花名册遍历上面的电话号码,依次发送一条短信通知他们。

上述例子中,发送短信通知就是一个发布-订阅模式,张三、王五等购买者都是订阅者,他们订阅房子开售的消息。售楼处作为发布者,会在合适的时候遍历花名册上的电话号码,依次给购房者发布消息。

上述例子存在的好处有:

  • 张三不需要每天都打电话过去询问,开发商手续完成后,售楼处会自动发送短信通知给张三;
  • 张三、李四等购买者与售楼处互不干扰,即使售楼MM辞职也不影响张三、李四等购买者,只要花名册还存在,售楼处就能给购房者发布消息。

回到代码层面上来,发布-订阅能给我们带来以下好处:

  • 发布、订阅逻辑代码解耦,两者互不影响,订阅逻辑的改动不影响发布逻辑,发布逻辑改动同样不影响订阅逻辑,它们存在一种动态的关系,增加了灵活性;
  • 支持简单的广播通信,自动通知所有已经订阅过的对象。

了解发布-订阅模式后,再回到mitt身上来。

mitt的介绍与使用

mitt的介绍

mitt是一个发布-订阅模式的库,并且它有9k的Star,优点有:

  • 轻量,压缩后体积小于200 bytes;
  • 支持通配符“*”事件类型侦听所有事件;
  • 与Node的EventEmitter相同的名称和想法。

mitt的使用

安装:

 npm install --save mitt

导入:

// 使用 ES6 模块化
import mitt from 'mitt'

// 使用CommonJs模块化
var mitt = require('mitt')

CDN:

<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>

mitt挂载至全局对象window

mitt API方法有:

  • on 订阅事件;
  • emit 发布事件;
  • off 取消订阅的事件。
import mitt from 'mitt'

const emitter = mitt()

// 订阅事件
emitter.on('foo', e => console.log('foo', e) )

// 订阅所有的事件
emitter.on('*', (type, e) => console.log(type, e) )

// 发布事件
emitter.emit('foo', { a: 'b' })

// 清空所有事件
emitter.all.clear()

function onFoo() {}
emitter.on('foo', onFoo)   // 订阅
emitter.off('foo', onFoo)  // 取消订阅事件

发布-订阅使用场景(包括但不限于):

  • 替代传递回调函数的方案,如订阅ajax请求的error、success等事件;
  • 页面某个动画,想在动画的每一帧完成之后做一些事,我们可以订阅一个事件,然后在动画每一帧完成之后发布这个事件;
  • 我有A页面、B页面,我在B页面订阅某个列表刷新,当我在A页面点页面返回按钮时,发布该事件触发B页面列表刷新。

这有一个小dome可以点击体验:mitt dome

实现mitt

mitt非常简单,源码也只有100行而且还是使用了TypeScript的情况下。

我去除掉TypeScript类型,将其修改成javaScript实现:

export default function mitt(all){
  //使用Map存储注册的事件
  all = all || new Map();

  return {
    all,
    on(type, handler) {
      const handlers= all!.get(type);
      if (handlers) {
        handlers.push(handler);
      }
      else {
        all!.set(type, [handler])
      }
    },
    
    off(type, handler) {
      const handlers = all!.get(type);
      if (handlers) {
        if (handler) {
          const index =  handlers.indexOf(handler)
          handlers.splice(index > -1 ? index : 0, 1);
        }
        else {
          all!.set(type, []);
        }
      }
    },
    
    emit(type, evt) {
      let handlers = all!.get(type);
      if (handlers) {
        handlers.slice().map((handler) => {
            handler(evt);
          });
      }
      //判断是否存在"*" 订阅的事件,"*"注册的事件进行兜底
      handlers = all!.get('*');
      if (handlers) {
        handlers.slice().map((handler) => {
            handler(type, evt);
          });
      }
    }
  };
}

all就是一个Map对象,它用于存储订阅事件。

相信大家理解这几行代码没啥难度。

这样我们就学会了一个拥有9k Star库的所有源码。

当然,咱们还能再给它扩展其他方法,如:once仅订阅一次,我也写过类似的发布-订阅模式medash/event.ts ,有兴趣的同学可以点它看看。

总结

本文先向读者介绍了发布-订阅模式相关理论,再给读者介绍发布-订阅模式社区现成的工具库mitt,最后从mitt的 100行源码中学习到实现发布-订阅模式相关逻辑。

如果我的文章对你有帮助,您的👍就是对我的最大支持^_^。

往期文章:http://linglan01.cn/about

本文含有隐藏内容,请 开通VIP 后查看