设计模式之发布订阅模式

2018-04-25  本文已影响0人  GrowthCoder

最近公司在进行设计模式的分享,形成文章记录下来,同时也是对自己学习的总结,便于回顾,以及与大家交流。

定义

他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知。

优势

缺点

发布-订阅 VS 观察者

发布-订阅是一种一对多的关系,一个对象状态发生改变时,所订阅他的对象也会得到通知;

观察者模式,观察者与被观察者耦合的比较多,被观察者(Subject)中可以包含很多观察者(Observer),并且可以调用观察者中的函数,以此来通知观察者,是一个主动推送的过程,观察者得到推送进行相关更新。
观察者模式

代码实现

发布—订阅模式可以用一个全局的 Event 对象来实现,订阅者不需要了解消息来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event 作为一个类似“中介者” 的角色,把订阅者和发布者联系起来。

class Event {
    constructor() {
        // 缓存列表
        this.clientList = {};
    }
    listen(key, fn) {
        // 订阅的消息添加进缓存列表
        (this.clientList[key] || (this.clientList[key] = [])).push(fn);
    }
    trigger() {
        const key = Array.prototype.shift.call(arguments),
              fns = this.clientList[key];

        if( !fns || fns.length == 0) {
            return false;
        }

        for(let i = 0, fn; fn = fns[i++]; ) {
            // 依次trigger 同一个key的回调
            fn.apply(this, arguments);
        }
    }
    remove(key, fn) {
        const fns = this.clientList[key];

        if(!fns) {
            // 如果key对应的消息没有被订阅 直接返回
            return false;
        }

        if( !fn ) {
            // 如果没有传回调函数
            // 则remove所有事件
            fns && (fns.length = 0); 
        }else{
            // 匹配到对应的回调函数 则删除
            fns.filter(item => item != fn);
        }
    }
}

const env = new Event();
env.listen('click', () => {
    console.log('aaa');
})
env.trigger('click', 'aa');
env.remove('click');
env.trigger('click'); 

使用场景

这个模式在前端领域是非常普遍的,尤其是在vue、vuex中

// vue中$on方法
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    // 如果是数组,遍历为每个event绑定$on方法,
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$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
// $off
// 注销event,如果没有参数,注销所有event,如果只传方法名,则注销方法名对应的所有event
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all 如果不传参注销所有event
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    // 如果event是数组则递归注销事件
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event];
    // 本身不存在该事件 则直接返回
    if (!cbs) {
      return vm
    }
    // 如果只传了event参数,则注销该event下所有事件
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // 如果传入了event以及fn,则遍历寻找对应的方法并删除
    if (fn) {
      // 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
  }

/* 存放订阅者 */
this._subscribers = [];

 /* 注册一个订阅函数,返回取消订阅的函数 */
  subscribe (fn) {
    const subs = this._subscribers
    if (subs.indexOf(fn) < 0) {
      subs.push(fn)
    }
    return () => {
      const i = subs.indexOf(fn)
      if (i > -1) {
        subs.splice(i, 1)
      }
    }
  }
  
  
 /* 调用mutation的commit方法 */
  commit (_type, _payload, _options) {
    // check object-style commit
    /* 校验参数 */
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    /* 取出type对应的mutation的方法 */
    const entry = this._mutations[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    /* 执行mutation中的所有方法 */
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    /* 通知所有订阅者 */
    this._subscribers.forEach(sub => sub(mutation, this.state))

    if (
      process.env.NODE_ENV !== 'production' &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }
必须先订阅后发布么?

有些情况下,需要先发布后订阅,比如QQ的离线消息,离线消息先缓存起来,等接受这上线之后,才可获取。

上一篇下一篇

猜你喜欢

热点阅读