[譯] 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索