编者注:建议在学习本文之前,先阅读一下【设计模式】-【观察者模式】

观察者模式和发布/订阅模式

通常在Javascript里,你可以把发布/订阅模式当成是观察者模式的一种实现。二者很相似,但他们之间的几点区别也是值得注意的。
我们先通过下面这张图来回顾一下观察者模式: 发布/订阅模式 - 图1图1
观察者模式中希望Observer必须订阅(Subscribe)内容改变的事件,Subject在内容改变后会通过Fire Event调用Observer中定义好的change事件,从而实现Observer和Subject的松耦合。

我们再来看一下发布/订阅模式:

发布/订阅模式 - 图2图2
图中的Publisher/Subscriber可以类比观察者模式中的Subject/Observer。发布/订阅模式使用了一个事件通道(EventChannel),这个通道介于Publisher和Subscriber之间。该事件通道允许代码定义应用程序的特定事件,事件可以传递自定义参数。这样做的目的是促进松散耦合,通过订阅另一个对象的特定任务或活动,当任务/活动发生时获得通知,而不是单个对象调用其他对象的方法。

如何使用

在上一章节中,我们大概了解发布/订阅模式的大致运行机制。从图2中,我们可以抽象出使用发布/订阅模式所需要的几个方法:

  • publish(eventName, params):publisher可以发布指定事件
  • subscribe(eventName, callback):subscriber可以订阅指定事件
  • unsubscribe(subscriber):取消订阅事件

下面的代码则模拟一个邮件收发的场景,来描述如何使用发布/订阅模式

  1. // 初始化订阅
  2. const eventName = 'message/in'
  3. // 订阅者1 打印邮件标题
  4. var subscriber1 = subscribe(eventName, function (topic, content) {
  5. console.log('i am receive an emial:', topic)
  6. })
  7. // 订阅者2 打印邮件内容
  8. var subscriber2 = subscribe(eventName, function (topic, content) {
  9. console.log('the content is:', content)
  10. })
  11. // 发出接收到邮件的事件
  12. publish(eventName, '邮件标题', '邮件内容')
  13. // 取消订阅
  14. unsubscribe(subscriber1)
  15. unsubscribe(subscriber2)

简单实现

  1. // 用于存储所有的订阅事件
  2. var eventList = {}
  3. // 事件通道
  4. var eventChannel = {
  5. // 发布事件的方法
  6. publish: function (eventName) {
  7. // 获取除了eventName之外的参数
  8. var params = Array.prototype.slice.call(arguments, 1, arguments.length)
  9. // 遍历所有的订阅 然后依次执行
  10. eventList[eventName] && eventList[eventName].forEach(function (callback) {
  11. // 支持多个参数传递
  12. callback.apply(null, params)
  13. })
  14. },
  15. // 订阅事件的方法
  16. subscribe: function (eventName, callback) {
  17. // 把需要订阅的callback添加到eventlist之中
  18. if (eventList[eventName]) {
  19. eventList[eventName].push(callback)
  20. } else {
  21. eventList[eventName] = [callback]
  22. }
  23. return {
  24. eventName: eventName,
  25. index: eventList.length - 1
  26. }
  27. },
  28. // 取消订阅的方法
  29. unsubscribe: function (subscriber) {
  30. if (subscriber) {
  31. eventList[subscriber.eventName].splice(subscriber.index, 1)
  32. subscriber = null
  33. }
  34. }
  35. }
  36. // 手动发布一个事件
  37. eventChannel.publish('sub', 'value1', 'value2')
  38. var subscriber1 = eventChannel.subscribe('sub', function (value1, value2) {
  39. // do anything you want.
  40. })
  41. var subscriber2 = eventChannel.subscribe('sub', function (value1, value2) {
  42. // do anything you want.
  43. })
  44. // 取消订阅
  45. eventChannel.unsubscribe(subscriber1)
  46. eventChannel.unsubscribe(subscriber2)

详细代码可以参考这个demo

实际应用场景

假设有一个负责显示实时股票的web应用,应用中有一个显示股票统计的网格和一个显示最后更新时间的计数器。当数据模型改变后,应用程序需要更新网格和计数器。这种情况下,数据模型就是发布主题的目标,观察者就是网格和计数器。
发布订阅模式将publisher和subscriber解藕,这样使得程序变得更易于扩展。如果要在界面的第三个区域根据数据模型展示一项新的内容,那么只需要添加一个subscriber就可以了,而不需要对publisher进行改动。