# 什么是发布—订阅模式

又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,我们一般用事件模型来替代传统的发布—订阅模式。

应用场景:

100个用户喜欢小四的写的小说,但是小四写小说需要时间,所以这100个人每隔一会就会问一下小四写完没(setInterval),小四卒...

小四的哥哥明姚也喜欢写小说,然后开通了一个微博,这100个用户都关注了(订阅),明姚写完发布一篇微博,用户都会收到(发布),时效性很强,且避免了用户的频繁无用询问

实际上 addEventListener 也算是发布—订阅模式:

//订阅 点击事件
document.addEventListener( 'click', function(){
  console.log(1)
}, false )

//发布 模拟点击事件/用户自己点击会发布
document.click()    // 模拟用户点击 

# 手动实现发布订阅模式

class Obeserver {
  constructor(){
    // 定义小说发布如  天龙八部、射雕英雄传
    this.callbacks = {}
  }
  $on(name, fn){
    // 监听  订阅某部小说
    this.callbacks[name] = this.callbacks[name] || []
    this.callbacks[name].push(fn)
  }
  $emit(name, ...args){
    // 触发   发布小说
    if(this.callbacks[name]){
      this.callbacks[name].forEach(cb=>cb(...args))
    }
  }
  $off(name, cb) {
    // 取消订阅
    const targetListen = this.callbacks[name]
    if (!targetListen) {
      // 如果 name 对应的小说没有被人订阅,则直接跳出
      return false
    }
    /* 会有两种情况。A和B都订阅了某一部小说,发布完成A会自己去看,B会通知给她女朋友看,所以要区分回调。 */
    if (!cb) {
      // 如果没有传入具体的要删除的回调函数,(注意回调函数应该是同一个引用地址,有名函数,作为参数作为$off第二个参数的实参)表示需要取消 name 对应小说的所有订阅
      targetListen && (targetListen.length = 0)
    } else {
      targetListen.forEach((el, idx) => {
        if (el === cb) {//两个方法应该是同一个引用地址
          /* 因为第一行是引用地址 直接删除也会将目标引用数据更改*/
          targetListen.splice(idx, 1) // 删除订阅者的回调函数
        }
      })
    }
  }
}

使用

const bus = new Obeserver()
// 等待发工资
bus.$on('fagongzi', function(e) {
  console.log('发工资了 %s', e)
})
// 财务打钱5毛
bus.$emit('fagongzi',  0.5)
// 钱太少了,跑路
bus.$off('fagongzi')
// 少发了5毛 再补上  这会已经跑路,所以接收不到通知了
bus.$emit('fagongzi',  0.5)

# vue已经实现发布订阅

  mounted() {
    //订阅
    this.$on('foo', e => {
      console.log(e)
    })
  },
  methods: {
    //点击发布
    handleClick() {
      this.$emit('foo', '我完了')
    }
  }