今天来总结一下如何手写一个简单的发布订阅模式。简单来说,发布订阅包含三大功能,包括监听事件、触发事件和取消监听事件。我们可以用两种方式来实现,分别是普通的对象和类实现:
普通实现
const eventHub = {
map: {},
on: (name, fn)=>{
//入队
//if(eventHub.map[name] === undefined){
// eventHub.map[name] = []
//}
eventHub.map[name] = eventHub.map[name] || []
eventHub.map[name].push(fn)
},
emit:(name, data)=>{
const q = eventHub.map[name]
if(!q){return}
q.map(f => f.call(undefined,data))
return undefined
}, //trigger
off:(name, fn)=>{
const p = eventHub.map[name]; //alias 设计模式
if (!p){return}
const index = p.indexOf(fn)
if(index<0){return}
p.splice(index, 1)
}
}
eventHub.on('click',f1) //监听事件,把f1放到任务队列中(先进先出)
eventHub.off('click',f1) //取消监听事件
setTimeout(()=>{
eventHub.emit('click','frank') //触发事件
},3000)
由于我们需要避免在多次调用发布订阅事件时顺序错乱的问题,所以我们构造了一个 map 作为任务队列。
其中,监听事件方法 on
我们需要判断是否任务队列中已经有相同的时间,如果没有就置为空数组,有的话就保留,然后 push 到 map 中。
而触发事件方法 emit
时,我们也需要判断是否任务队列中有相对应的事件,如果没有,则直接返回;如果有,则通过 map
方法找到每一个函数,并一一执行,最后 return undefined
(因为一般不关心返回值)。
对于取消监听事件方法 off
,同样的如果任务队列中并没有相对应的事件,那就直接返回;反之则定义一个 index
作为函数 fn 的下标,如果函数不存在,即下标小于零,则直接返回;反之则通过数组的 splice
方法去除相对应下标的函数。
类实现
除了普通的实现方法,也可以使用 JS 的类方法来实现发布订阅:
class EventHub {
map = {}
on(name, fn) {
this.map[name] = this.map[name] || []
this.map[name].push(fn)
}
emit(name, data) {
const fnList = this.map[name] || []
fnList.forEach(fn => fn.call(undefined, data))
}
off(name, fn) {
const fnList = this.map[name] || []
const index = fnList.indexOf(fn)
if(index < 0) return
fnList.splice(index, 1)
}
}
// 使用
const e = new EventHub()
e.on('click', (name)=>{
console.log('hi '+ name)
})
e.on('click', (name)=>{
console.log('hello '+ name)
})
setTimeout(()=>{
e.emit('click', 'frank')
},3000)
整体思路是一样的,但是在方法的实现上会有类和函数的一些区别。