前言:
发布订阅模式是一种软件设计模式,其中发布者负责发布消息,而订阅者订阅自己感兴趣的消息,当发布者发布消息时,所有订阅该消息的订阅者都会收到通知并执行相应的操作,实现了消息的解耦和系统组件之间的松散耦合。
发布订阅模式可解耦消息发布与订阅,实现组件松散耦合。
发布订阅模式用于解耦消息,组件耦合松散。
它是一种消息范式,涉及消息的发送者(称为发布者)和接收者(称为订阅者)。在这种模式中,发布者和订阅者不直接相互了解,而是通过一个称为"事件通道"或"消息代理"的中间人来管理消息的分发。
开始手写
准备容器
constructor() {
this.cache = {}
}
使用了es6 class
的方式去完成本次手写,每当我们去实例一个对象时,都会调用constructor()
收集订阅事件
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn)
} else {
this.cache[name] = [fn]
}
}
对于on
方法,我们可以简单地以视频平台的博主与粉丝之间的联系来理解。
对于博主来说,想要接受到他发布的动态或者视频,用户首先得订阅他的账号,这样在每次博主更新自己的内容时,用户都能够收到信息。
同样的对于本段代码来说,name
是指博主的账号,而fn
可以抽象地理解为用户账号,由于一个博主可以被多个用户关注订阅,所以就解释了name
对于的fn为什么是一个数组。
而当不存在name
的值时,首先对他进行一个初始化。
执行事件
emit(name, once = false, ...args) {
if (this.cache[name]) {
// 执行 但不要影响订阅者
let tasks = this.cache[name].slice()
for (let fn of tasks) {
fn(...args)
}
if (once) {
delete this.cache[name]
}
}
}
对于emit
方法同样的我们以视频平台的例子来理解。
每当博主发布了自己的动态或者视频时,需要对每一个订阅了他账号的用户推送自己的内容
因此,首先判断容器(cache
)中是否存在博主账号,存在时利用silce
实现一个浅拷贝的效果,不影响容器
直接遍历所有的粉丝(fn
),传入对应的参数,推送自己的信息(执行函数)
额外还有个可选的参数,once
,如果传入的值为true
,表示只对当前账号执行一次操作,并且在操作完成后对账号进行删除。
移除事件
off(name, fn) {
let tasks = this.cache[name]
if (tasks) {
const index = tasks.findIndex(f => f === fn || f.callback === fn)
if (index >= 0) {
tasks.splice(index, 1)
}
}
}
对于用户来说有了关注,自然也有了取消关注的选项
但在博主的视角来看,我们需要从所有的粉丝列表当中找出,想要取关的账号,然后再将这个账号移除,以便在之后推送自己内容时,不会再推送到取消关注的用户。
在代码层面来看,先拿到整个粉丝列表,使用迭代器findIndex
直接快速找到,取关的账号(fn
),由于findIndex
在查找不到时,会返回-1,所以当index的值大于等于0时,表示找到了该粉丝(fn
),直接使用splice
影响原数组删除。
订阅一次
once(name, fn) {
// 创建一个新的回调函数,该函数会在执行后自动取消订阅
const wrappedFn = (...args) => {
this.off(name, wrappedFn);
fn(...args);
};
this.on(name, wrappedFn);
}
有的时候,即使用户没有订阅博主但却依旧可以浏览博主发布的内容,由于必须要先订阅才能看到博主的内容。这时,我们可以直接先造出一个虚假的关注博主的粉丝,以他为跳板浏览博主发布的信息。
这个虚假的粉丝就是wrappedFn
,在用户执行完毕后直接将其off
取消订阅,就实现了只订阅一次的功能。
整体代码
//EventEmitter
class EventEmitter {
constructor() {
this.cache = {}
}
//收集订阅
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn)
} else {
this.cache[name] = [fn]
}
}
//执行事件
emit(name, once = false, ...args) {
if (this.cache[name]) {
// 执行 但不要影响订阅者
let tasks = this.cache[name].slice()
for (let fn of tasks) {
fn(...args)
}
if (once) {
delete this.cache[name]
}
}
}
//移除订阅
off(name, fn) {
let tasks = this.cache[name]
if (tasks) {
const index = tasks.findIndex(f => f === fn || f.callback === fn)
if (index >= 0) {
tasks.splice(index, 1)
}
}
}
//执行一次
once(name, fn) {
// 创建一个新的回调函数,该函数会在执行后自动取消订阅
const wrappedFn = (...args) => {
this.off(name, wrappedFn);
fn(...args);
};
this.on(name, wrappedFn);
}
}
总结
发布-订阅模式在前端开发中有许多实际应用场景,它提供了一种松散耦合的机制,允许不同模块或组件之间进行灵活的通信(例如vue2中的事件总线通信,vue3的响应式实现等)。