结合vue讲述发布订阅模式

81 阅读2分钟
发布订阅模式的定义

发布订阅模式是一种设计模式,允许对象之间的消息传递,而不需要彼此显示引用,通常用于松耦合的事件处理系统。

发布订阅模式的基本概念和vue的实现分析
  1. 发布者: 发布者是事件的生产者,它在某个时间发生时通知用户
// vue怎么进行发布?
通过this.$emit, 执行所有订阅该event的事件队列
this.$emit = function(event){
    const cbs = _event[event]
    cbs.forEach(cb =>{
        cb.apply(vm, arguments)
    })
}
  1. 订阅者: 订阅者是事件的消费者,它对某个事件感兴趣,并在事件发生时执行某些操作
// vue怎么进行订阅
// 通过this.$on,注册事件队列,并往事件队列中注册操作
// 在事件管理对象_events上,设置属性,值为一个数组。数组中存放的是订阅者的操作
this.$on = function(event, fn) {
    _event[event] || (_event[event] = []).push(fn)
}
  1. 事件通道: 事件通道是发布者和订阅者的桥梁,负责管理事件的发布和订阅
// 事件通道指什么?
// 事件通道指_event, 是一个普通的对象,通过管理该对象上的属性和属性的值进行管理。
_event: Record<sting, Array<Function>> = Object.create(null)
源码分析
// 初始化事件管理对象
export function initEvents(vm: Component) {
  // 事件管理,每个事件名称对应一个监听器数组
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}
// 订阅
Vue.prototype.$on = function (
    event: string | Array<string>,
    fn: Function
  ): Component {
    const vm: Component = this
    if (isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      ;(vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }
// 发布
 Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (__DEV__) {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
            `${formatComponentName(
              vm
            )} but the handler is registered for "${event}". ` +
            `Note that HTML attributes are case-insensitive and you cannot use ` +
            `v-on to listen to camelCase events when using in-DOM templates. ` +
            `You should probably use "${hyphenate(
              event
            )}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
// 管理事件通道
Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on() {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  Vue.prototype.$off = function (
    event?: string | Array<string>,
    fn?: Function
  ): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event!]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event!] = null
      return vm
    }
    // specific handler
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }
简单实现一个发布订阅模式
// 定义事件通道类
class EventEmitter {
  private events: { [key: string]: Array<(...args: any[]) => void> } = {};

  // 订阅事件
  on(event: string, listener: (...args: any[]) => void) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  // 取消订阅事件
  off(event: string, listener: (...args: any[]) => void) {
    if (!this.events[event]) return;
    this.events[event] = this.events[event].filter(l => l !== listener);
  }

  // 发布事件
  emit(event: string, ...args: any[]) {
    if (!this.events[event]) return;
    this.events[event].forEach(listener => listener(...args));
  }
}

// 创建事件通道实例
const eventEmitter = new EventEmitter();

// 订阅者订阅事件
eventEmitter.on('data', (data) => {
  console.log('Data received:', data);
});

// 发布者发布事件
eventEmitter.emit('data', { message: 'Hello, World!' });

// 取消订阅事件
const listener = (data: any) => console.log('Another listener:', data);
eventEmitter.on('data', listener);
eventEmitter.off('data', listener);