- 原文地址:The Publisher/Subscriber Pattern in JavaScript
- 原文做者:jsmanifest
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:EmilyQiRabbit
- 校对者:weisiwu,Alfxjx
简写为 Pub 和 subjavascript
在本篇文章中,咱们将会学习 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
事件被触发的时候,它将会收到两个参数:数组
eventName
参数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 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。