# 9.手写消息订阅与发布
# 9.1.API说明
- PubSub: 包含所有功能的订阅/发布消息的管理者
 - PubSub.subscribe(msg, subscriber): 订阅消息: 指定消息名和订阅者回调函数
 - PubSub.publish(msg, data): 异步发布消息: 指定消息名和数据
 - PubSub.publishSync(msg, data): 同步发布消息: 指定消息名和数据
 - PubSub.unsubscribe(flag): 取消订阅: 根据标识取消某个或某些消息的订阅
 
# 9.2.编码实现
/* 
自定义消息订阅与发布
*/
const PubSub = {}
/* 
  {
    add: {
      token1: callback1, 
      token2: callback2
    },
    update: {
      token3: callback3
    }
  }
*/
let callbacksObj = {} // 保存所有回调的容器
let id = 0 // 用于生成token的标记
// 1. 订阅消息
PubSub.subscribe = function (msgName, callback) {
  // 确定token
  const token = 'token_' + ++id
  // 取出当前消息对应的callbacks
  const callbacks = callbacksObj[msgName]
  if (!callbacks) {
    callbacksObj[msgName] = {
      [token]: callback
    }
  } else {
    callbacks[token] = callback
  }
  // 返回token
  return token
}
// 2. 发布异步的消息
PubSub.publish = function (msgName, data) {
  // 取出当前消息对应的callbacks
  let callbacks = callbacksObj[msgName]
  // 如果有值
  if (callbacks) {
    // callbacks = Object.assign({}, callbacks)
    // 启动定时器, 异步执行所有的回调函数
    setTimeout(() => {
      Object.values(callbacks).forEach(callback => {
        callback(data)
      })
    }, 0)
  }
}
// 3. 发布同步的消息
PubSub.publishSync = function (msgName, data) {
  // 取出当前消息对应的callbacks
  const callbacks = callbacksObj[msgName]
  // 如果有值
  if (callbacks) {
    // 立即同步执行所有的回调函数
    Object.values(callbacks).forEach(callback => {
      callback(data)
    })
  }
}
/*
4. 取消消息订阅
  1). 没有传值, flag为undefined
  2). 传入token字符串
  3). msgName字符串
*/
PubSub.unsubscribe = function (flag) {
  // 如果flag没有指定或者为null, 取消所有
  if (flag === undefined) {
    callbacksObj = {}
  } else if (typeof flag === 'string') {
    if (flag.indexOf('token_') === 0) { // flag是token
      // 找到flag对应的callbacks
      const callbacks = Object.values(callbacksObj).find(callbacks => callbacks.hasOwnProperty(flag))
      // 如果存在, 删除对应的属性
      if (callbacks) {
        delete callbacks[flag]
      }
    } else { // flag是msgName
      delete callbacksObj[flag]
    }
  } else {
    throw new Error('如果传入参数, 必须是字符串类型')
  }
}
export default PubSub
# 9.3.测试
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>手写消息订阅与发布</title>
</head>
<body>
<script src="../dist/atguigu-utils.js"></script>
<script>
  const { PubSub } = aUtils
  // 订阅消息
  PubSub.subscribe('add', (data) => {
    console.log('add()...', data)
  })
  PubSub.subscribe('add', (data) => {
    console.log('add2()...', data)
  })
  const token = PubSub.subscribe('add', (data) => {
    console.log('add3()...', data)
  })
  PubSub.subscribe('update', (data) => {
    console.log('update()...', data)
  })
  // PubSub.unsubscribe(token)
  // PubSub.unsubscribe('add')
  // PubSub.unsubscribe()
  PubSub.publish('add', 12)
  // PubSub.publish('update', 13)
  PubSub.publishSync('update', 12)
  console.log('----')
</script>
</body>
</html>
← 8.手写事件总线 10.手写Promise →