我们先了解什么是发布-订阅模式,发布-订阅模式它是一种对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到状态的通知。
发布-订阅模式流程如下:
- 订阅者将自己想订阅的事件注册到调度中心;
- 当发布者发布该事件到调度中心时,调度中心执行订阅者注册的事件。
举一个生活中的例子:
张三最近看上一套房子,到售楼处才被告知,该楼盘已售罄。好在售楼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