目錄
概念
它定義了一種一對多的關係,讓多個訂閱者對象同時監聽一個發佈者。這個發佈者的狀態發生變化時就會通知所有訂閱自己的訂閱者對象,使它們能夠自動更新自己
舉例:
我們關注了一個公衆號。那麼,關注這個公衆號的所有人都會收到公衆號的推送。
這就是一個典型的發佈-訂閱模式。我們訂閱了公衆號,公衆號有消息發佈出來,我們都會收到。這裏,我們是訂閱者,公衆號是發佈者。
特點:
- 我們(訂閱者)只需要關注一下公衆號(發佈者),就可以在某個時間接收公衆號(發佈者)的推送,不需要一直詢問(輪詢)是否有新消息;
- 公衆號後臺有一個訂閱者的列表,發送消息時會根據列表發送給關注的人。當訂閱者增加或減少時,列表會進行相應的改變;
- 一個人可以關注多個公衆號;不同公衆號產生消息時,會相應的通知訂閱了不同類型消息的人;
代碼實現:
1. 事件監聽函數addEventListener
addEventListener
window.addEventListener('load', function() {
console.log('load')
})
這段代碼很常見,我們經常將一些操作掛載在onload
上,當頁面元素資源加載完畢,就會觸發onload
事件上的回調。
這也是一種發佈-訂閱模式,我們無法預知頁面何時加載完畢,但可以通過window
的onload
事件,它會在加載完畢時向訂閱者發佈消息(即執行回調函數)
2. 使用jQuery
自帶的API(on
,trigger
,off
)來實現事件的訂閱,發佈,取消;
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
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
)該數據的訂閱者列表裏的 Watcher
,Watcher
會觸發組件重渲染(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()
是訂閱和發佈的具體方法
簡單來說,響應式數據是消息的發佈者,而視圖層是消息的訂閱者,如果數據更新了,那麼發佈者會發布數據更新的消息來通知視圖更新,從而實現數據層和視圖層的雙向綁定。
發佈-訂閱模式的優缺點
優點:實現解耦
- 時間方面,註冊的訂閱行爲由消息的發佈方來決定何時調用,訂閱者不用持續關注,當消息發生時發佈者會負責通知;
- 對象方面,發佈者無需知道消息的接受者是誰,只需遍歷訂閱列表發送消息即可
缺點:
- 消耗內存,創建結構和緩存訂閱者會消耗計算和內存資源
- 複雜:多個訂閱者和發佈者層層嵌套,使得程序難以追蹤和調試