liangbus / blogging

Blog to go
10 stars 0 forks source link

能否手写一个发布/订阅模式? #41

Open liangbus opened 4 years ago

liangbus commented 4 years ago

能否手写一个发布/订阅模式?

这个问题还经常在网上看到,碰巧之前有看过一个类似的库的源码,就复习一下,写了个简单的能用的消息订阅组件

其实原理很简单,就是通过一个对象去把订阅的事件去存起来,然后触发的时候,把订阅过的事件都执行一遍就好了,需要注意的是,订阅源可能会有多个,因此需通过一个自加变更去避免覆盖,这也利用了闭包的特性。

以下是可以直接使用的代码

(function(root, factory){
  root.onfire = factory()
})(window, function(){
  var __eventList = {}
  var __counter = 0
  function on(eventName, callback, context = window) {
    return __bind(eventName, callback, context)
  }
  function __bind(eventName, callback, context = window, isOnce = false) {
    if(!__eventList[eventName]) {
      __eventList[eventName] = {}
    }
    __eventList[eventName][++__counter] = [callback, context, isOnce]
    return [eventName, __counter]
  }
  function fire(eventName) {
    var args = [].slice.call(arguments, 1)
    __invokeFunc(eventName, ...args)
  }
  function __invokeFunc(eventName) {
    var __triggerEvent = __eventList[eventName]
    if(!__triggerEvent) return
    var args = [].slice.call(arguments, 1)
    // 遍历列表
    for(let key in __triggerEvent) {
      var eventTarget = __triggerEvent[key]
      if (eventTarget) {
        eventTarget[0].apply(eventTarget[1], args)
        // if is invoked onced, when it done, reset it
        if(eventTarget[2]) {
          __triggerEvent[key] = null
        }
      }
    }
  }
  function remove(eventTarget) {
    var __triggerEvent = __eventList[eventTarget[0]]
    __triggerEvent[eventTarget[1]] = null
  }
  function once(eventName, callback, context) {
    return __bind(eventName, callback, context, true)
  }
  return {
    on,
    fire,
    once,
    remove
  }
})

与观察者模式异同

观察者模式在实际中常见的应用有 DOM 事件的监听

其实观察者模式跟发布订阅模式很相似,都是一对多的依赖关系

而主要的区别就是,发布订阅模式,会有一个存储事件的消息中心,也就是上面 __eventList 对象,发布者不需要知会订阅者存不存在,只需要关心自己发布的消息即可

观察者模式则需要提前声明好观察者对象,并加入监听的队列中,当发布者发布消息之后,触发观察者相应的方法

相比起来,发布订阅更加灵活,耦合性更低,所以在实际开发中用得更加广泛

下面是简单的观察者模式实现

class Subject {
  constructor() {
    this.subsQueue = []
  }
  add(obs) {
    this.subsQueue .push(obs)
  }
  notify(data) {
    this.subsQueue .forEach(subTarget => {
      subTarget .update(data)
    })
  }
}

class Observer {
  update(data) {
    console.log('updated: ', data)
  }
}