一、什么是发布订阅模式?
发布订阅模式中,可以有多个订阅者,但却只能有一个发布者。当发布者发布某一新消息时,所有的订阅者都可以接收到该消息。
具体实现过程可大致分为:
- 订阅者先在事件调度中心里注册事件(想订阅的内容)和回调函数
- 发布者将具体事件发送到事件调度中心来触发相关事件
- 事件调度中心处理相应的回调函数并将结果发送给对应的订阅者
为了方便理解,我们举个栗子:
aDiv.addEventListener('click',function(){
console.log('一个朴实无华的div');
})
DOM中给元素绑定点击事件就是一种发布订阅模式。你不用关心这个div什么时候被点击,因为它被点击的时候会自动触发回调函数。
二、实现它o_O
相信大家都或多或少看过直播,我们这里就以某牙举例。
在某牙里,一旦你订阅了某个主播,那么当主播开播的时候,平台就会给你和其他订阅者推送开播消息。
//定义发布者
let huya = {
//缓存列表(事件调度中心),
//存放被订阅的主播和回调函数
anchorLists: {},
//订阅主播
//fn --- 系统给用户发送的消息通知
listen: function (anchorName, fn) {
//如果缓存列表里没有对应的主播(当第一个人订阅该主播时),
//我们就在缓存列表中为其创建一个专属数组放置订阅者们
if (!this.anchorLists[anchorName]) {
this.anchorLists[anchorName] = []
}
this.anchorLists[anchorName].push(fn)
},
//发布消息
trigger: function () {
//这里由于不确定推送消息时是否需要携带其它参数,所以利用
//shift方法返回要发送开播提醒的主播名
let anchorName = Array.prototype.shift.call(arguments)
//如果缓存列表中没有该主播的专属数组,说明没有人订阅他
if (!this.anchorLists.hasOwnProperty(anchorName)) {
console.log(`该主播尚未被订阅…o_O…`);
return;
}
//如果有人订阅他,取出该主播的专属数组并执行其中所有的回调函数来通知各个订阅者
let fnQueues = [...this.anchorLists[anchorName]]
fnQueues.forEach(item => (item.apply(this, arguments)))
}
}
这样,发布、订阅就完成了,但是我们应该还有一些额外的功能,比如:取消订阅、仅订阅一次。
取消订阅
remove: function (anchorName, fn) {
if (!this.anchorLists[anchorName] || this.anchorLists[anchorName].length===0) {
console.log(`你尚未订阅该主播`);
return;
}
//获取缓存列表中该主播的专属数组
let fnLists = [...this.anchorLists[anchorName]]
//订阅者们触发remove()时系统应该强制将其注册的消息通知函数(即fn)传入,不然如果一个用户
//取消订阅,那么所有订阅了主播的用户的消息通知函数都将被清除。这个操作应该只有系统才有权限执行。
//虽然这里与我们的场景有些许冲突,但是其他场景里这么判断还是有必要的,所以问题不大。
if (!fn) {
fnLists && (fnLists.length = 0)
}
for (let i = 0; i < fnLists.length; i++) {
var obj = fnLists[i];
if (obj === fn) {
//利用循环找到该用户注册的消息通知函数并将其删除
fnLists.splice(i, 1)
}
}
//刷新该主播的专属数组
this.anchorLists[anchorName] = fnLists;
}
这样的话,我们取消订阅的功能也完成了。
仅订阅一次
once(anchorKey, fn) {
function cdb() {
fn.apply(this, arguments)
this.remove(anchorKey, cdb)
}
this.listen(anchorKey, cdb)
}
这里因为我们先前已经写好了订阅和取消订阅的方法,所以我们直接在once中用就行了,不过要注意的是,因为订阅一次功能要求消息推送函数(fn)要先执行一次再取消订阅,所以我们将消息推送函数的执行和取消订阅事件封装进一个函数(cdb)中来达到此目的。
三、测试结果
这里在node环境下运行
//订阅者小明
function xiaoming() {
let args = Array.prototype.slice.call(arguments)
console.log(`xiaoming,${args}开始直播了`);
}
//订阅者张三
function zhangsan() {
let args = Array.prototype.slice.call(arguments)
console.log(`zhangsan,${args}开始直播了`);
}
对once功能进行测试
//订阅者们订阅相关事件
huya.listen('uzi',xiaoming)
huya.once('uzi',zhangsan)
//系统发布消息
huya.trigger('uzi','乌兹')
huya.trigger('uzi','乌兹')
对remove功能进行测试
huya.listen('uzi',xiaoming)
huya.listen('uzi',zhangsan)
huya.trigger('uzi','乌兹')
//小明取消订阅uzi
huya.remove('uzi',xiaoming)
huya.trigger('uzi','乌兹')
当连续多次取消订阅同个主播时,也会打印信息:‘你尚未订阅该主播’(亲测有效)。
到这儿,我们的发布订阅模式就完成了,不足之处还请大家多多指教(手动抱拳)。