手写发布订阅

85 阅读2分钟
  1. 如何理解:实现一个对象,是通信的中介,里面有三个方法:分别是on(监听)、off(关闭监听)、emit(触发)
  2. 例如:监听一个click事件,在三秒后触发某事件,代码雏形如下
const eventHub={
  //这三个函数都没人关心其返回值,所以都return undefined
  on:(name,fn)=>{return undefined},
  emit:(name,data)=>{return undefined},
  off:(name,fn)=>{return undefined}
}

eventHub.on('click',f1)
eventHub.off('click',f1)

setTimeout(()=>{
  eventHub.emit('click','lanlan')
},3000)
  1. 实现on,即监听了某事件,就将该事件放入任务队列里(用哈希表map,因为任务队列里还有队列(如click队列、右键队列、左键队列等),即一个name对应一个数组,映射关系)
const eventHub={
  map:{},
  on:(name,fn)=>{
    eventHub.map[name]=eventHub.map[name]||[]  //先做判断,要入的这个队是否为空,若空则初始化
    eventHub.map[name].push(fn)  //入队
  },
  emit:(name,data)=>{return undefined},
  off:(name,fn)=>{return undefined}
}
  1. 实现off,即取消监听某事件,就将该事件从任务队列里删除
const eventHub={
  map:{},
  on:(name,fn)=>{
    eventHub.map[name]=eventHub.map[name]||[]  
    eventHub.map[name].push(fn)  
  },
  emit:(name,data)=>{return undefined},
  off:(name,fn)=>{
    if(!eventHub.map[name]){return}  //先做判断,若队列为空,直接return
    const index=eventHub.map[name].indexOf(fn)
    if(index<0){return}  //若该事件不在队列里,直接return
    eventHub.map[name].splice(index,1)  //若在,删除即可
  }
}
  1. off里的eventHub.map[name]可以用q代替(alias设计模式),但on里的不行,因为涉及到初始化改写
const eventHub={
  map:{},
  on:(name,fn)=>{
    eventHub.map[name]=eventHub.map[name]||[]  
    eventHub.map[name].push(fn)  
  },
  emit:(name,data)=>{return undefined},
  off:(name,fn)=>{
    const q=eventHub.map[name]
    if(!q){return}  
    const index=q.indexOf(fn)
    if(index<0){return} 
    q.splice(index,1)  
  }
}
  1. 实现emit,即调用队列里的函数
const eventHub={
  map:{},
  on:(name,fn)=>{
    eventHub.map[name]=eventHub.map[name]||[]  
    eventHub.map[name].push(fn)  
  },
  emit:(name,data)=>{
    const q=eventHub.map[name]  //map里可能是这样:  click:[f1,f2],所以要遍历调用
    if(!q){return}  //也是要判断队列空不空,空的话直接return
    q.map(f=>f.call(undefined,data))  //不空就直接遍历调用,forEach和map都是遍历,区别是map有返回值,forEach没有
    return undefined
  },
  off:(name,fn)=>{
    const q=eventHub.map[name]
    if(!q){return}  
    const index=q.indexOf(fn)
    if(index<0){return} 
    q.splice(index,1)  
  }
}
  1. 最终如下:

微信图片_20240522130640.png 8. 还可以用class实现

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)