[译] JavaScript 的发布者/订阅者(Publisher/Subscriber)模式

JavaScript 的发布者/订阅者(Publisher/Subscriber)模式

简写为 Pub 和 subjavascript

**Photo by [NordWood Themes](https://unsplash.com/@nordwood) on [Unsplash](https://unsplash.com/)**

在本篇文章中,咱们将会学习 JavaScript 的发布/订阅模式,而且咱们将能看到,在咱们的 JavaScript 代码中使用这种设计模式很简单(但却很高效)。前端

发布者/订阅者模式是一种设计模式,旨在让开发者设计出不直接互相依赖,但却能够互相传递信息的高效能动态应用程序。java

这种模式在 JavaScript 中十分常见,它和观察者模式的工做方式很相近 —— 一个区别是,在观察者模式中,观察者直接从它所观察的实体那里获得通知,而在发布者/订阅者模式中,订阅者则经过渠道获得通知,渠道位于发布者和订阅者之间并来回传递信息。android

若是想要实现一个发布者/订阅者模式,咱们须要一个发布者、一个订阅者,以及一些存储订阅者所注册的回调函数的空间。ios

下面,让咱们一块儿来看看落实到代码应该怎么写。咱们将会使用一个 factory 函数(你不必定非要使用这个模式)来写出发布者/订阅者模式的的实现。git

咱们要作的第一件事,就是在函数中声明一个本地变量,用来保存订阅的回调函数:github

function pubSub() {
  const subscribers = {}
}
复制代码

下面,咱们将会定义 subscribe 方法,它负责在 subscribers 中插入回调函数:后端

function pubSub() {
  const subscribers = {}
  
  function subscribe(eventName, callback) {
    if (!Array.isArray(subscribers[eventName])) {
      subscribers[eventName] = []
    }
    subscribers[eventName].push(callback)
  }
  
  return {
    subscribe,
  }
}
复制代码

该段代码会在添加新的事件订阅前,检查订阅者中是否有对应的事件。若是没有,将在订阅者上添加值为空数组的事件属性,不然将在对应的事件队列下入队新的订阅回调。设计模式

publish 事件被触发的时候,它将会收到两个参数:数组

  1. eventName 参数
  2. 全部被传递给注册在 subscribers[eventName] 的回调函数的 data

咱们继续向下看看代码是如何实现的:

function pubSub() {
  
  const subscribers = {}
  
  function publish(eventName, data) {
    if (!Array.isArray(subscribers[eventName])) {
      return
    }
    subscribers[eventName].forEach((callback) => {
      callback(data)
    })
  }
  
  function subscribe(eventName, callback) {
    if (!Array.isArray(subscribers[eventName])) {
      subscribers[eventName] = []
    }
    subscribers[eventName].push(callback)
  }
  
  return {
    publish,
    subscribe,
  }
}
复制代码

在遍历 subscribers 中的回调函数列表以前,要先检查对象中该属性是不是数组类型。若是不是,那么就认为这个 eventName 以前并无被注册过,因此就直接返回了。这一步能够保证避免潜在的程序报错。

在这以后,若是程序执行到了 .forEach 这一行,那么咱们就能够肯定 eventName 已经被注册了一个或多个回调函数。程序就能够继续保证安全的循环遍历 subscribers[eventName]

程序每读取到一个回调函数,都将会以 data 为第二个参数来调用它。

当咱们如此订阅一个函数的时候,就会发生上述流程。

function showMeTheMoney(money) {
  console.log(money)
}

const ps = pubSub()

ps.subscribe('show-money', showMeTheMoney)
复制代码

若是咱们在之后的某一时刻调用 publish

ps.publish('show-money', 1000000)
复制代码

那么咱们注册的 showMeTheMoney 回调函数将会被触发,money 参数的值为 1000000

function showMeTheMoney(money) {
  console.log(money) // result: 10000000
}
复制代码

这就是发布/订阅模式的原理。咱们定义了一个 pubSub 函数,并在函数内将回调函数存储到本地,并提供了 subscribe 方法注册回调函数,以及 publish 方法来遍历并使用数据来调用全部注册过的回调函数。

可是,这里还存在一个问题。在真正应用这个模式的时候,若是咱们订阅了不少回调函数,就可能会遇到内存泄漏,若是不想办法解决这个问题,这将形成极大的浪费。

因此咱们还须要一个移除订阅的回调函数的方法,以便在不须要它们的时候能够删除。一般的方法是在某处定义一个 unsubscribe 方法。而实现它最便捷的位置就是做为 subscribe 的返回值,由于在我看来这是最直观的方法,咱们来看看代码:

function subscribe(eventName, callback) {
  if (!Array.isArray(subscribers[eventName])) {
    subscribers[eventName] = []
  }
  
  subscribers[eventName].push(callback)
  
  const index = subscribers[eventName].length - 1
  
  return {
    unsubscribe() {
      subscribers[eventName].splice(index, 1)
    },
  }
}

const { unsubscribe } = subscribe('food', function(data) {
  console.log(`Received some food: ${data}`)
})

// 移除订阅的回调
unsubscribe()
复制代码

在这个例子中,咱们须要一个索引。这样咱们就能确保移除的是正确的回调函数,咱们使用得是 .splice 函数,它能够经过索引来移除咱们须要移除的数组中的项目。

咱们还能够这样写;可是这样性能就稍差一些:

function subscribe(eventName, callback) {
  if (!Array.isArray(subscribers[eventName])) {
    subscribers[eventName] = []
  }
  
  subscribers[eventName].push(callback)
  
  const index = subscribers[eventName].length - 1
  
  return {
    unsubscribe() {
      subscribers[eventName] = subscribers[eventName].filter((cb) => {
        // 在新的数组中再也不包含这个回调函数
        if (cb === callback) {
          return false
        }
        return true
      })
    },
  }
}
复制代码

不足之处

虽然发布者/订阅者模式有不少优点,可是同时它也存在灾难性的缺点,这可能会让咱们付出巨大的调试时间成本。

咱们如何知道是否以前已经订阅了同一个回调函数呢?除非咱们实现一个工具来映射整个列表,不然实在没法知道,可是这样的话咱们就要使用 JavaScript 来完成更多的工做了。

在实际应用中过分使用发布者/订阅者模式,也让咱们的代码更加难以维护。事实是,在这种模式中回调函数之间是解耦的,因此当你在多处都使用了回调函数后,追踪代码就变得很是困难。


总结

综上所述就是本文的所有内容。但愿能对你有所帮助!

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索