Skip to content

全局事件的命名冲突

全局的发布—订阅对象里只有一个 clinetList 来存放消息名和回调函数,大家都通过它来订 阅和发布各种消息,久而久之,难免会出现事件名冲突的情况,所以我们还可以给 Event 对象提 供创建命名空间的功能。

javascript
class EventBus {
  constructor () {
    this.events = {}
    this.offlineStack = {}
  }

  listen (key, fn) {

    // 订阅多个事件
    if (Array.isArray(key)) {
      for (let i = 0, l = key.length; i < l; i++) {
        this.listen(key[i], fn)
      }
      return this
    }

    const { namespace, event } = getNamespace(key)

    this.events[namespace] = this.events[namespace] || {};
    (
      this.events[namespace][event] || 
      (this.events[namespace][event] = [])
    ).push(fn)

    // 处理离线事件
    if (
      this.offlineStack[namespace]?.[event] &&
      this.offlineStack[namespace][event].length
    ) {
      let cbs = this.offlineStack[namespace][event]

      for (let i = 0, l = cbs.length; i < l; i++) {
        cbs[i]()
      }

      this.offlineStack[namespace][event] = null
    }

    return this
  }

  trigger (key, ...args) {
    const { namespace, event } = getNamespace(key)
    const cbs = this.events[namespace]?.[event]

    if (!cbs) {
      this.offlineStack[namespace] = this.offlineStack[namespace] || {};
      (
        this.offlineStack[namespace][event] || 
        (this.offlineStack[namespace][event] = [])
      ).push(() => {
        this.trigger(key, ...args)
      })
    } else {
      for (let i = 0, l = cbs.length; i < l; i++) {
        cbs[i].call(this, ...args)
      }
    }

    return this
  }

  one (key, fn) {
    if (Array.isArray(key)) {
      for (let i = 0, l = key.length; i < l; i++) {
        const k = key[i]
        const cb = (...args) => {
          fn.call(this, ...args)
          this.remove(k, fn)
        }
        cb.fn = fn
    
        this.listen(k, cb)
      }
    }
  }

  remove (key, fn) {
    if (arguments.length === 0) {
      this.events = Object.create(null)
      return this
    }

    if (Array.isArray(key, fn)) {
      for (let i = 0, k; k = key[i++];) {
        this.remove(k, fn)
      }
      return this
    }

    const { namespace, event } = getNamespace(key)
    const cbs = this.events[namespace]?.[event]

    if (!cbs) {
      return this
    } 

    if (!fn) {
      this.events[namespace][event] = null
      return this
    } 

    for (let i = cbs.length - 1; i >= 0; i--) {
      const cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return this
  }
}

const getNamespace = event => {
  let namespace = 'default'
  const parts = event.split(':')

  if (parts.length > 1) {
    namespace = parts.shift()
    event = parts[0]
  }

  return {
    namespace,
    event
  }
}