Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
liangbus opened this issue Apr 16, 2020 · 0 comments
Open

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

liangbus opened this issue Apr 16, 2020 · 0 comments

Comments

@liangbus
Copy link
Owner

liangbus commented Apr 16, 2020

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

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

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

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

(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)
  }
}
@liangbus liangbus changed the title 能否手写一个消息订阅? 能否手写一个发布/订阅模式? Apr 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant