一、概述
發(fā)布訂閱模式是一種常用的設(shè)計(jì)模式,它定義了一種一對多的關(guān)系,讓多個(gè)訂閱者對象同時(shí)監(jiān)聽某一個(gè)主題對象,當(dāng)主題對象發(fā)生變化時(shí),它會通知所有訂閱者對象,使它們能夠自動(dòng)更新 。
二、優(yōu)缺點(diǎn)
1. 優(yōu)點(diǎn):
- 實(shí)現(xiàn)了發(fā)布者和訂閱者之間的解耦,提高了代碼的可維護(hù)性和復(fù)用性。
- 支持異步處理,可以實(shí)現(xiàn)事件的延遲觸發(fā)和批量處理。
- 支持多對多的通信,可以實(shí)現(xiàn)廣播和組播的功能。
2. 缺點(diǎn):
- 可能會造成內(nèi)存泄漏,如果訂閱者對象沒有及時(shí)取消訂閱,就會一直存在于內(nèi)存中。
- 可能會導(dǎo)致程序的復(fù)雜性增加,如果訂閱者對象過多或者依賴關(guān)系不清晰,就會增加程序的調(diào)試難度。
- 可能會導(dǎo)致信息的不一致性,如果發(fā)布者在通知訂閱者之前或之后發(fā)生了變化,就會造成數(shù)據(jù)的不同步。
三、適用場景
發(fā)布訂閱模式適用于以下場景:
- 當(dāng)一個(gè)對象的狀態(tài)變化需要通知其他多個(gè)對象時(shí),可以使用發(fā)布訂閱模式來實(shí)現(xiàn)松耦合的通信
- 當(dāng)一個(gè)事件或消息需要廣泛傳播或分發(fā)給多個(gè)接收者時(shí),可以使用發(fā)布訂閱模式來實(shí)現(xiàn)高效的消息分發(fā)
- 當(dāng)一個(gè)系統(tǒng)需要支持異步處理或批量處理時(shí),可以使用發(fā)布訂閱模式來實(shí)現(xiàn)事件的延遲觸發(fā)或批量觸發(fā)
四、代碼示例
在JavaScript中,實(shí)現(xiàn)發(fā)布訂閱模式的基本思想是:
- 定義一個(gè)發(fā)布者對象,它有一個(gè)緩存列表,用于存放訂閱者對象的回調(diào)函數(shù)
- 定義一個(gè)訂閱方法,用于向緩存列表中添加回調(diào)函數(shù)
- 定義一個(gè)取消訂閱方法,用于從緩存列表中移除回調(diào)函數(shù)
- 定義一個(gè)發(fā)布方法,用于遍歷緩存列表,依次執(zhí)行回調(diào)函數(shù),并傳遞相關(guān)參數(shù)
下面是一個(gè)簡單的發(fā)布訂閱模式的代碼示例 :
// 定義一個(gè)發(fā)布者對象
var pub = {
// 緩存列表,存放訂閱者回調(diào)函數(shù)
list: {},
// 訂閱方法
subscribe: function(key, fn) {
// 如果沒有該消息的緩存列表,就創(chuàng)建一個(gè)空數(shù)組
if (!this.list[key]) {
this.list[key] = [];
}
// 將回調(diào)函數(shù)推入該消息的緩存列表
this.list[key].push(fn);
},
// 取消訂閱方法
unsubscribe: function(key, fn) {
// 如果有該消息的緩存列表
if (this.list[key]) {
// 遍歷緩存列表
for (var i = this.list[key].length - 1; i >= 0; i--) {
// 如果存在該回調(diào)函數(shù),就從緩存列表中刪除
if (this.list[key][i] === fn) {
this.list[key].splice(i, 1);
}
}
}
},
// 發(fā)布方法
publish: function() {
// 獲取消息類型
var key = Array.prototype.shift.call(arguments);
// 獲取該消息的緩存列表
var fns = this.list[key];
// 如果沒有訂閱該消息,就返回
if (!fns || fns.length === 0) {
return;
}
// 遍歷緩存列表,執(zhí)行回調(diào)函數(shù)
for (var i = 0; i < fns.length; i++) {
fns[i].apply(this, arguments);
}
}
};
// 定義一個(gè)訂閱者對象A
var subA = function(name) { console.log('A收到了消息:' + name); };
// 定義一個(gè)訂閱者對象B
var subB = function(name) { console.log('B收到了消息:' + name); };
// A訂閱了test消息
pub.subscribe('test', subA);
// B訂閱了test消息
pub.subscribe('test', subB);
// 發(fā)布了test消息,傳遞了參數(shù) 'hello'
pub.publish('test', 'hello');
// 輸出:
// A收到了消息:hello
// B收到了消息:hello
// A取消訂閱了test消息
pub.unsubscribe('test', subA);
// 發(fā)布了test消息,傳遞了參數(shù) 'world'
pub.publish('test', 'world');
// 輸出: // B收到了消息:world
五、 Vue2 響應(yīng)式系統(tǒng)實(shí)現(xiàn)原理
在 Vue
中,每個(gè)組件實(shí)例都有相應(yīng)的 watcher
實(shí)例對象,它會在組件渲染的過程中把屬性記錄為依賴,之后當(dāng)依賴項(xiàng)的 setter
被調(diào)用時(shí),會通知 watcher
重新計(jì)算,從而致使它關(guān)聯(lián)的組件得以更新。
1. 監(jiān)聽器
實(shí)現(xiàn)一個(gè)方法,這個(gè)方法會對需要監(jiān)聽的數(shù)據(jù)對象進(jìn)行遍歷、給它的屬性加上定制的 getter
和 setter
函數(shù)。這樣但凡這個(gè)對象的某個(gè)屬性發(fā)生了改變,就會觸發(fā) setter
函數(shù),進(jìn)而通知到訂閱者。
// observe 方法遍歷并包裝對象屬性
function observe(target) {
// 若target是一個(gè)對象,則遍歷它
if(target && typeof target === 'object') {
Object.keys(target).forEach((key)=> {
// defineReactive方法會給目標(biāo)屬性裝上“監(jiān)聽器”
defineReactive(target, key, target[key])
})
}
}
// 定義 defineReactive 方法
function defineReactive (obj, key, val) {
/* 一個(gè) Dep 類對象 */
const dep = new Dep();
/* 屬性值也可能是 object 類型,這種情況下需要調(diào)用 observe 進(jìn)行遞歸遍歷 */
observe(val);
// 為當(dāng)前屬性安裝監(jiān)聽器
Object.defineProperty(obj, key, {
// 可枚舉
enumerable: true,
// 不可配置
configurable: true,
get: function reactiveGetter () {
/* 將 Dep.target(即當(dāng)前的 Watcher 對象存入 dep 的 subs 中) */
dep.addSub(Dep.target);
return val;
},
// 監(jiān)聽器函數(shù)
set: function reactiveSetter (newVal) {
if (newVal === val) return;
/* 在 set 的時(shí)候觸發(fā) dep 的 notify 來通知所有的 Watcher 對象更新視圖 */
dep.notify();
}
});
}
2. 訂閱者
// 定義訂閱者類 Dep
class Dep {
constructor () {
/* 初始化訂閱隊(duì)列 */
this.subs = [];
}
/* 增加訂閱者,在 subs 中添加一個(gè) Watcher 對象 */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有 Watcher 對象更新視圖 */
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
3. 觀察者
class Watcher {
constructor () {
/* 在 new 一個(gè) Watcher 對象時(shí)將該對象賦值給 Dep.target,在 get 中會用到 */
Dep.target = this;
}
/* 更新視圖的方法 */
update () {
console.log("視圖更新啦~");
}
}
Dep.target = null;
4. Vue 組裝
class Vue {
constructor(options) {
this._data = options.data;
observe(this._data);
/* 新建一個(gè) Watcher 觀察者對象,這時(shí)候 Dep.target 會指向這個(gè) Watcher 對象 */
new Watcher();
/* 在這里模擬 render 的過程,為了觸發(fā) test 屬性的 get 函數(shù) */
console.log('render~', this._data.test);
}
}
let vm = new Vue({
data:{
test:"origin"
}
});
vm._data.test="測試更改!";
/* 運(yùn)行結(jié)果
* render~ origin
* 視圖更新啦~
* "測試更改!"
*/
六、實(shí)現(xiàn)一個(gè) Event Bus / Event Emitter
Event Bus / Event Emitter 作為全局事件總線,它起到的是一個(gè)溝通橋梁的作用。我們可以把它理解為一個(gè)事件中心,我們所有事件的訂閱/發(fā)布都不能由訂閱方和發(fā)布方“私下溝通”,必須要委托這個(gè)事件中心幫我們實(shí)現(xiàn)。文章來源:http://www.zghlxwxcb.cn/news/detail-742208.html
class EventEmitter {
constructor() {
// handlers是一個(gè)map,用于存儲事件與回調(diào)之間的對應(yīng)關(guān)系
this.handlers = {}
}
// on方法用于安裝事件監(jiān)聽器,它接受目標(biāo)事件名和回調(diào)函數(shù)作為參數(shù)
on(eventName, cb) {
// 先檢查一下目標(biāo)事件名有沒有對應(yīng)的監(jiān)聽函數(shù)隊(duì)列
if (!this.handlers[eventName]) {
// 如果沒有,那么首先初始化一個(gè)監(jiān)聽函數(shù)隊(duì)列
this.handlers[eventName] = []
}
// 把回調(diào)函數(shù)推入目標(biāo)事件的監(jiān)聽函數(shù)隊(duì)列里去
this.handlers[eventName].push(cb)
}
// emit方法用于觸發(fā)目標(biāo)事件,它接受事件名和監(jiān)聽函數(shù)入?yún)⒆鳛閰?shù)
emit(eventName, ...args) {
// 檢查目標(biāo)事件是否有監(jiān)聽函數(shù)隊(duì)列
if (this.handlers[eventName]) {
// 這里需要對 this.handlers[eventName] 做一次淺拷貝,主要目的是為了避免通過 once 安裝的監(jiān)聽器在移除的過程中出現(xiàn)順序問題
const handlers = this.handlers[eventName].slice()
// 如果有,則逐個(gè)調(diào)用隊(duì)列里的回調(diào)函數(shù)
handlers.forEach((callback) => {
callback(...args)
})
}
}
// 移除某個(gè)事件回調(diào)隊(duì)列里的指定回調(diào)函數(shù)
off(eventName, cb) {
const callbacks = this.handlers[eventName]
const index = callbacks.indexOf(cb)
if (index !== -1) {
callbacks.splice(index, 1)
}
}
// 為事件注冊單次監(jiān)聽器
once(eventName, cb) {
// 對回調(diào)函數(shù)進(jìn)行包裝,使其執(zhí)行完畢自動(dòng)被移除
const wrapper = (...args) => {
cb(...args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
}
}
// 使用方法
const eventBus = new EventEmitter()
eventBus.on('test', (val) => {
console.log(val, "===test")
})
eventBus.emit('test', 21) // 輸出: 21,===test
總結(jié)
發(fā)布訂閱模式是一種常用的設(shè)計(jì)模式,它可以實(shí)現(xiàn)對象間的松耦合通信,支持異步處理和多對多的通信。它也有一些缺點(diǎn),比如可能會造成內(nèi)存泄漏、程序復(fù)雜性增加和信息不一致性。在使用發(fā)布訂閱模式時(shí),需要注意合理地設(shè)計(jì)發(fā)布者和訂閱者之間的關(guān)系,避免出現(xiàn)不必要的問題。文章來源地址http://www.zghlxwxcb.cn/news/detail-742208.html
到了這里,關(guān)于js設(shè)計(jì)模式——發(fā)布訂閱模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!