概念

发布订阅是是一种设计模式,在这个设计模式中相当于有一个中介,负责事件的发布和订阅
我们把这个中介取名为 eventHub, 在 eventHub 中通常有 on(订阅), emit(发布), off(取消订阅) 等方法

  1. const eventHub = {
  2. on: (name, fn) => {},
  3. emit: (name, data) => {},
  4. off: (name, fn) => {}
  5. }

我们还需要一个数据结构用来存储事件和对应的方法, 一种事件可以有多个订阅,每个订阅都对应不同的方法,且这些方法应该是要按订阅的顺序执行的,即这些方法是一个任务队列,我们可以把用数组来存储任务队列,
每个事件名都对应一个数组,就形成了映射,也称为哈希,因此我们声明一个eventMap

  1. const eventMap = {}

on

先来写订阅方法 on
如果 eventMap[name] 不存在则初始化为空数组 []
并将 fn 插入到数组中

  1. on: (name, fn) => {
  2. eventMap[name] = eventMap[name] || []
  3. eventMap[name].push(fn)
  4. }

off

再写取消订阅方法
如果事件和方法存在,则删除

  1. off: (name, fn) => {
  2. if (!eventMap[name]) { return }
  3. const index = eventMap[name].indexOf(fn)
  4. if ( index >= 0) {
  5. eventMap[name].splice(index, 1)
  6. }
  7. }

emit

发布方法 emit

  1. emit: (name, data) => {
  2. if (!eventMap[name]) { return }
  3. eventMap[name].map(f => f.call(undefined, data))
  4. }

使用

这样一个完整的eventHub就实现了

  1. const eventMap = {}
  2. const eventHub = {
  3. on: (name, fn) => {
  4. eventMap[name] = eventMap[name] || []
  5. eventMap[name].push(fn)
  6. },
  7. emit: (name, data) => {
  8. if (!eventMap[name]) { return }
  9. eventMap[name].map(f => f.call(undefined, data))
  10. },
  11. off: (name, fn) => {
  12. if (!eventMap[name]) { return }
  13. const index = eventMap[name].indexOf(fn)
  14. if ( index >= 0) {
  15. eventMap[name].splice(index, 1)
  16. }
  17. }
  18. }

我们来使用一下这个 eventHub

  1. // 订阅
  2. eventHub.on('click', console.log)
  3. eventHub.on('click', console.error)
  4. // 发布
  5. eventHub.emit('click', 'hello')

结果如下
image.png

once

我们还可以给eventHub添加一个 once 方法,once 的作用是只订阅一次,当触发事件后,自动取消订阅

  1. once: (name, fn) => {
  2. const _fn = () => {
  3. fn()
  4. eventHub.off(name, _fn)
  5. }
  6. eventHub.on(name, _fn)
  7. }