JS設計模式之發佈-訂閱模式

目錄

概念

它定義了一種一對多的關係,讓多個訂閱者對象同時監聽一個發佈者。這個發佈者的狀態發生變化時就會通知所有訂閱自己的訂閱者對象,使它們能夠自動更新自己

舉例:

我們關注了一個公衆號。那麼,關注這個公衆號的所有人都會收到公衆號的推送。
這就是一個典型的發佈-訂閱模式。我們訂閱了公衆號,公衆號有消息發佈出來,我們都會收到。這裏,我們是訂閱者,公衆號是發佈者。

特點:

  • 我們(訂閱者)只需要關注一下公衆號(發佈者),就可以在某個時間接收公衆號(發佈者)的推送,不需要一直詢問(輪詢)是否有新消息;
  • 公衆號後臺有一個訂閱者的列表,發送消息時會根據列表發送給關注的人。當訂閱者增加或減少時,列表會進行相應的改變;
  • 一個人可以關注多個公衆號;不同公衆號產生消息時,會相應的通知訂閱了不同類型消息的人;

代碼實現:

1. 事件監聽函數addEventListener

window.addEventListener('load', function() {
    console.log('load')
})

這段代碼很常見,我們經常將一些操作掛載在onload上,當頁面元素資源加載完畢,就會觸發onload事件上的回調。
這也是一種發佈-訂閱模式,我們無法預知頁面何時加載完畢,但可以通過windowonload事件,它會在加載完畢時向訂閱者發佈消息(即執行回調函數)

2. 使用jQuery自帶的API(on,trigger,off)來實現事件的訂閱,發佈,取消;

function eventHandler() {
    console.log('您訂閱的消息來嘍!')
}
// 事件訂閱
$('#app').on('myEvent', eventHandler)
// 發佈
$('#app').trigger('myEvent')
// 輸出:您訂閱的消息來嘍!

// 取消訂閱
$('#app').off('myEvent')
$('#app').trigger('myEvent')
// 無輸出

3. 原生方式

function eventHandler() {
    console.log('您訂閱的消息來嘍!')
}

var app = document.getElementById('app')
// 事件訂閱
app.addEventListener('myEvent', eventHandler)
// 發佈
app.dispatchEvent(new Event('myEvent'))
// 輸出:您訂閱的消息來嘍!

// 取消訂閱
app.removeEventListener('myEvent',eventHandler)
app.dispatchEvent(new Event('myEvent'))
// 無輸出

4. 用JS自己實現一下

案例:一家零食小鋪,“喫貨們”發現“絕味鴨脖”等零食售罄了!於是留了個電話給店主,店主給記在小本本上,等有貨的時候,再通過小本本打電話通知“喫貨們”

class eventHandler{
    constructor() {
        this.list = {}
    }

    // 消息訂閱
    subscribe(type, fn) {
        if(this.list[type]){
            if(!this.list[type].includes(fn)){
                this.list[type].push(fn)
            }
        }else{
            this.list[type]=[fn]
        }
    }

    // 消息退訂
    unsubscribe(type, fn) {
        if(!this.list[type] || !this.list[type].includes(fn))return
        let index = this.list[type].indexOf(fn)
        this.list[type].splice(index,1)

    }

    // 消息發佈
    notify(type, info){
        if(!this.list[type])return
        this.list[type].forEach(fn => fn(info))
    }
}

const shop = new eventHandler()

// 小白先生和大黃女士 預訂絕味鴨脖
shop.subscribe('絕味鴨脖', message => console.log('小白先生——' + message)) 
shop.subscribe('絕味鴨脖', f1 = message => console.log('大黃女士——' + message)) 
// 翠花 預訂泡椒鳳爪
shop.subscribe('泡椒鳳爪', message => console.log('翠花——' + message)) 

console.log('進貨了,打電話通知買家:')
shop.notify('絕味鴨脖','絕味鴨脖進貨了!')
shop.notify('泡椒鳳爪','泡椒鳳爪進貨了!')

console.log('絕味鴨脖賣完了,通知買家:')
shop.notify('絕味鴨脖','絕味鴨脖賣完了!')

console.log('沒買到鴨脖的大黃,憤怒的退訂了')
shop.unsubscribe('絕味鴨脖', f1)

console.log('進貨了,打電話通知買家:')
shop.notify('絕味鴨脖','絕味鴨脖進貨了!')

輸出:
在這裏插入圖片描述

5. Vue的EventBus

vue有父子組件通信,兄弟組件通信。
父子組件通信中:父組件通過props向下傳遞數據給子組件,子組件可以通過$emit事件通知父組件;
兄弟組件通信中:如果不使用Vuex來共享數據,那我們可以使用vue中的事件總線EventBus來通信
代碼示例:
組件B發送消息給組件A
創建event-bus.js文件:

// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()

組件A使用:

// 組件A
import { EventBus } from "./event-bus.js";

EventBus.$on("myevent", args => {
		console.log(args)
})

組件B使用:

// 組件B
import { EventBus } from "./event-bus.js";

EventBus.$emit("myevent", 'some args')

6. vue源碼中的發佈-訂閱模式

vue利用發佈-訂閱模式來實現數據層和視圖層的雙向綁定
在這裏插入圖片描述
組件渲染函數(Component Render Function)被執行前,會對數據層的數據進行響應式化。響應式化大致就是使用 Object.defineProperty 把數據轉爲 getter/setter,併爲每個數據添加一個訂閱者列表的過程。這個列表是getter閉包中的屬性,將會記錄所有依賴這個數據的組件。

每個組件都對應一個 Watcher訂閱者。當每個組件的渲染函數被執行時,都會將本組件的 Watcher 放到自己所依賴的響應式數據的訂閱者列表裏,這就相當於完成了訂閱,一般這個過程被稱爲依賴收集(Dependency Collect

當響應式數據發生變化的時候,也就是觸發了 setter時,setter會負責通知(Notify)該數據的訂閱者列表裏的 WatcherWatcher會觸發組件重渲染(Trigger re-render)來更新(update)視圖

vue源碼如下,大致瞭解一下:

// src/core/observer/index.js
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val // 如果原本對象擁有getter方法則執行
      if (Dep.target) {
        dep.depend()   // 進行依賴收集,dep.addSub
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)  // 如果原本對象擁有setter方法則執行
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()  // 如果發生變更,則通知更新
    }
  })

上面的dep.depend()dep.notify() 是訂閱和發佈的具體方法
簡單來說,響應式數據是消息的發佈者,而視圖層是消息的訂閱者,如果數據更新了,那麼發佈者會發布數據更新的消息來通知視圖更新,從而實現數據層和視圖層的雙向綁定。
在這裏插入圖片描述

發佈-訂閱模式的優缺點

優點:實現解耦

  • 時間方面,註冊的訂閱行爲由消息的發佈方來決定何時調用,訂閱者不用持續關注,當消息發生時發佈者會負責通知;
  • 對象方面,發佈者無需知道消息的接受者是誰,只需遍歷訂閱列表發送消息即可

缺點:

  • 消耗內存,創建結構和緩存訂閱者會消耗計算和內存資源
  • 複雜:多個訂閱者和發佈者層層嵌套,使得程序難以追蹤和調試

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章