发布订阅模式是一种常见的设计模式,通常用于前端开发中。该模式基于一种事件驱动的机制,其中一个对象(通常为发布者)可以发布事件,而其他对象(通常称为订阅者)则可以订阅该事件,以便在该事件发生时进行相应的操作。
在前端开发中,发布订阅模式通常用于解耦和组件通信。例如,一个组件可以发布一个事件,其他组件则可以订阅该事件,以便在事件发生时更新其状态或执行其他操作。这使得代码更具有可维护性和可扩展性,因为每个组件都可以独立地处理自己的逻辑,而不必与其他组件直接交互。
简单来说,发布订阅通常包含三个主要功能:监听事件(订阅事件 on)、取消监听事件(取消订阅 off)和触发事件(发布事件 emit)
事件驱动机制:通常指的是一种基于事件的编程模式,即通过触发事件来驱动代码的执行。例如通过监听 DOM 事件来响应鼠标点击、键盘输入等操作,或者通过监听 HTTP 请求的返回结果来更新页面内容。
解耦:通常指的是将不同组件之间的依赖降到最低,以减少代码之间的耦合度。
组件通信:指的是组件之间的信息传递,包括数据传递、状态同步、事件触发等。
普通实现
- 创建一个eventHub对象并实现发布订阅的三个方法,然后调用。
const eventHub = { on: ()=>{}, emit: ()=>{}, off: ()=>{} } eventHub.on('click', f1) eventHub.off('click', f1) setTimeout(()=>{ eventHub.emit('click', 'data') }, 3000) - 当有多个对象订阅时应该怎么处理?如:
eventHub.on('click', f1)
eventHub.on('click', f2)
我们可以通过队列实现先入先出,并通过map实现映射。
所以我们定义一个map={}作为任务队列。 - 实现on方法:根据调用得知接受事件名和函数两个参数(name, fn)。
然后执行入队操作:
初始化:eventHub.map[name] = eventHub.map[name] || []
入队:eventHub.map[name].push(fn) - 实现off方法:
判断eventHub.map[name]是否存在,不存在则return。
若eventHub.map[name]存在则判断是否存在fn。
若fn存在则使其出队。 - 实现emit方法:
判断任务队列中是否有相对应的事件,没有则返回。反之则通过map方法找到每一个函数,并一一执行,最后return undefined(因为一般不关心返回值)。
// 最终代码实现
const eventHub = {
map:{
// click:[f1, f2]
},
on: (name, fn)=>{
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
},
off: (name, fn)=>{
const q = eventHub.map[name] //alias 设计模式
if(!q){return}
const index = q.indexOf(fn)
if(index < 0){return}
q.splice(index,1)
}
}
eventHub.on('click', f1)
eventHub.off('click', f1)
setTimeout(()=>{
eventHub.emit('click', 'data')
}, 3000)
类实现
// 思路与上面基本一致。
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', 'data')
},3000)