2023-06-06
前端
00

目录

mitt的介绍与使用
mitt的介绍
mitt的使用
实现mitt
总结

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

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

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

举一个生活中的例子:

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

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

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

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

上述例子存在的好处有:

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

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

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

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

mitt的介绍与使用

mitt的介绍

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

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

mitt的使用

安装:

Bash
npm install --save mitt

导入:

JavaScript
// 使用 ES6 模块化 import mitt from 'mitt' // 使用CommonJs模块化 var mitt = require('mitt')

CDN:

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

mitt挂载至全局对象window

mitt API方法有:

  • on 订阅事件;
  • emit 发布事件;
  • off 取消订阅的事件。
JavaScript
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实现:

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

本文作者:凌览

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!