【Javascript】設計模式之發布訂閱模式

文章目錄

  • 1、現實中的發布-訂閱模式
  • 2、DOM 事件
  • 3、簡單的發布-訂閱模式
  • 4、通用的發布-訂閱模式
  • 5、先發布再訂閱
  • 6、小結

發布—訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都將得到通知。在 JavaScript 開發中,我們一般用事件模型來替代傳統的發布—訂閱模式

1、現實中的發布-訂閱模式

小明最近看上了一套房子,到了售樓處之后才被告知,該樓盤的房子早已售罄。好在售樓
處告訴小明,不久后還有一些尾盤推出。于是小明離開之前,把電話號碼留在了售樓處,相同的還有小紅,小強。于是新樓盤推出的時候,售樓處會翻開花名冊,遍歷上面的電話號碼,依次發送一條短信來通知他們

2、DOM 事件

只要我們曾經在 DOM 節點上面綁定過事件函數,那我們就曾經使用過發布—訂閱模式

document.body.addEventListener( 'click', function(){ alert(2); 
}, false ); document.body.click(); // 模擬用戶點擊

3、簡單的發布-訂閱模式

發布-訂閱模式的實現步驟
1、定義發布者
2、給發布者添加一個緩存列表,用于存放回調函數以便通知訂閱者
3、最后發布消息的時候,發布者會遍歷這個緩存列表,依次觸發里面存放的訂閱者回調函數

代碼示例:

var salesOffices = {}; // 定義發布者
salesOffices.clientList = []; // 緩存列表,存放訂閱者的回調函數
salesOffices.listen = function (fn) {// 增加訂閱者this.clientList.push(fn); // 訂閱的消息添加進緩存列表
};
salesOffices.trigger = function () {// 發布消息for (var i = 0; i < this.clientList.length; i++) {var fn = this.clientList.length;fn.apply(this, arguments); // arguments 是發布消息時帶上的參數}
};

測試:

salesOffices.listen(function (price, squareMeter) {// 小明訂閱消息console.log('小明價格= ' + price);console.log('小明squareMeter= ' + squareMeter);
});
salesOffices.listen(function (price, squareMeter) {// 小紅訂閱消息console.log('小紅價格= ' + price);console.log('小紅squareMeter= ' + squareMeter);
});salesOffices.trigger(2000, 300);
salesOffices.trigger(2000, 700);

問題:
訂閱者接收到了發布者發布的每個消息,有些并不是訂閱者需要的

解決:
要增加一個標示 key,讓訂閱者只訂閱自己感興趣的消息:
改寫代碼:

var salesOffices = {}; // 定義發布者
salesOffices.clientList = {}; // 緩存對象,存放訂閱者的回調函數
salesOffices.listen = function (key, fn) {if (!this.clientList[key]) {// 如果還沒有訂閱過此類消息,給該類消息創建一個緩存列表this.clientList[key] = [];}this.clientList[key].push(fn); // 訂閱的消息添加進消息緩存列表
};
salesOffices.trigger = function () {// 發布消息var key = Array.prototype.shift.call(arguments); // 取出消息類型var fns = this.clientList[key]; // 取出該消息對應的回調函數集合if (!fns || fns.length === 0) {// 如果沒有訂閱該消息,則返回return false;}for (var i = 0; i < fns.length; i++) {var fn = fns[i];fn.apply(this, arguments); // (2) // arguments 是發布消息時附送的參數}
};

測試:

salesOffices.listen('squareMeter88', function (price) {// 小明訂閱 88 平方米房子的消息console.log('價格= ' + price); // 輸出: 2000000
});
salesOffices.listen('squareMeter110', function (price) {// 小紅訂閱 110 平方米房子的消息console.log('價格= ' + price); // 輸出: 3000000
});salesOffices.trigger('squareMeter88', 30000);
salesOffices.trigger('squareMeter110', 70000);

4、通用的發布-訂閱模式

包含:發布-訂閱,取消訂閱

var Event = {clientList: {},listen: function (key, fn) {if (!this.clientList[key]) {this.clientList[key] = [];}this.clientList[key].push(fn);},trigger: function () {var key = Array.prototype.shift.call(arguments);var fns = this.clientList[key];if (!fns || fns.length === 0) {return false;}for (var i = 0, fn; (fn = fns[i++]); ) {fn.apply(this, arguments);}},// 增加 remove 方法remove(key, fn) {var fns = this.clientList[key];if (!fns) {return false;}if (!fn) {fns && (fns.length = 0);} else {for (var i = fns.length - 1; i >= 0; i--) {var _fn = fns[i];if (fn === _fn) {fns.splice(i, 1);}}}},
};

測試:

var f1 = function (price) {console.log('價格= ' + price);
};
Event.listen('s88', f1);var f2 = function (price) {console.log('價格= ' + price);
};
Event.listen('s110', f2);Event.remove('s110', f2); // 刪除訂閱Event.trigger('s88', 30000);
Event.trigger('s110', 70000);

5、先發布再訂閱

應用場景:發布者發布的內容,不管訂閱者在發布之前訂閱,或者發布之后訂閱,都可觸發訂閱者訂閱的內容

代碼:

var Event = (function () {var clientList = {};var offlineStack = {}; // 離線緩存參數var triggerStack = {}; // 已觸發trigger的參數緩存var listen;var trigger;var remove;listen = function (key, fn) {if (!clientList[key]) {clientList[key] = [];}clientList[key].push(fn);// 如果此時訂閱的事件,已經發布了,則自定觸發一次訂閱內容(fn)if (triggerStack[key]) {fn.apply(this, triggerStack[key]);} else if (offlineStack[key]) {// 如果是離線狀態,則觸發事件fn.apply(this, offlineStack[key]);}};trigger = function () {var key = Array.prototype.shift.call(arguments);var fns = clientList[key];if (fns) {// 已經有人訂閱此事件,將參數緩存//(假如有些訂閱者比較晚訂閱,且發布者已經發布過了,那么這個訂閱者訂閱的時候,自動觸發一次訂閱內容)triggerStack[key] = [...arguments];for (var i = 0; i < fns.length; i++) {fns[i].apply(this, arguments);}} else {// 表示當前還沒有人訂閱此事件,則先將參數緩存起來offlineStack[key] = [...arguments];}};// 取消訂閱remove = function (key, fn) {var fns = this.clientList[key];if (!fns) {return false;}if (!fn) {// 如果沒有傳入具體的回調函數,表示需要取消 key 對應消息的所有訂閱fns && (fns.length = 0);} else {for (var l = fns.length - 1; l >= 0; l--) {var _fn = fns[l];if (_fn === fn) {fns.splice(l, 1);}}}};return {listen: listen,trigger: trigger,remove: remove,};
})();

測試1:先訂閱,再發布

// 先訂閱
Event.listen('test1', function (a) {console.log('我是發布之前的訂閱者1:', a);
});
Event.listen('test1', function (a) {console.log('我是發布之前的訂閱者2:', a);
});
// 再發布
Event.trigger('test1', 12);// 我是發布之前的訂閱者1: 12
// 我是發布之前的訂閱者2: 12

測試2:先發布,再訂閱

// 先發布
Event.trigger('test1', 12);// 再訂閱
Event.listen('test1', function (a) {console.log('我是發布之后的訂閱者1:', a);
});
Event.listen('test1', function (a) {console.log('我是發布之后的訂閱者2:', a);
});// 我是發布之后的訂閱者1: 12
// 我是發布之后的訂閱者2: 12

測試3:先訂閱,再發布,再訂閱

// 先訂閱
Event.listen('lis1', function (a) {console.log('我是發布之前的訂閱者1:', a);
})
Event.listen('lis1', function (a) {console.log('我是發布之前的訂閱者2:', a);
})// 再發布
console.log('---第1次發布');
Event.trigger('lis1', 123);
console.log('---第1次發布完成');// 再訂閱
Event.listen('lis1', function (b) {console.log('我是發布之后的訂閱者~:', b);
})// ---第1次發布
// 我是發布之前的訂閱者1: 123
// 我是發布之前的訂閱者2: 123
// ---第1次發布完成
// 我是發布之后的訂閱者~: 123

測試4:先發布,再訂閱,再發布,再訂閱

// 先發布
console.log('------第1次發布-------');
Event.trigger('lis1', 123);// 再訂閱
Event.listen('lis1', function (a) {console.log('我是發布之后的訂閱者1:', a);
})
Event.listen('lis1', function (a) {console.log('我是發布之后的訂閱者2:', a);
})// 再發布
console.log('------第2次發布-------');
Event.trigger('lis1', 456);// 再訂閱
Event.listen('lis1', function (a) {console.log('我是發布之后的再次訂閱者1:', a);
})
Event.listen('lis1', function (a) {console.log('我是發布之后的再次訂閱者2:', a);
})// ------第1次發布-------
// 我是發布之后的訂閱者1: 123
// 我是發布之后的訂閱者2: 123// ------第2次發布-------
// 我是發布之后的訂閱者1: 456
// 我是發布之后的訂閱者2: 456
// 我是發布z之后的再次訂閱者1: 456
// 我是發布z之后的再次訂閱者2: 456

測試5:先訂閱,再發布,再訂閱,再發布

Event.listen('lis1', function (a) {console.log('我是發布之前的訂閱者1:', a);
})
Event.listen('lis1', function (a) {console.log('我是發布之前的訂閱者2:', a);
})console.log('---第1次發布');
Event.trigger('lis1', 123);
console.log('---第1次發布完成');Event.listen('lis1', function (b) {console.log('我是發布之后的訂閱者~:', b);
})console.log('---第2次發布');
Event.trigger('lis1', 456);
console.log('---第2次發布完成');// ---第1次發布
// 我是發布之前的訂閱者1: 123
// 我是發布之前的訂閱者2: 123
// ---第1次發布完成
// 我是發布之后的訂閱者~: 123// ---第2次發布
// 我是發布之前的訂閱者1: 456
// 我是發布之前的訂閱者2: 456
// 我是發布之后的訂閱者~: 456
// ---第2次發布完成

6、小結

優點:
一為時間上的解耦,二為對象之間的解耦

缺點:
1、創建訂閱者本身要消耗一定的時間和內存,而且當你訂閱一個消息后,也許此消息最后都未發生,但這個訂閱者會始終存在于內存中
2、如果過度使用的話,對象和對象之間的必要聯系也將被深埋在背后,會導致程序難以跟蹤維護和理解

應用:
應用非常廣泛,既可以用在異步編程中,也可以幫助我們完成更松耦合的代碼編寫

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/717296.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/717296.shtml
英文地址,請注明出處:http://en.pswp.cn/news/717296.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mysql深入學習 基礎篇 Ss.02 詳解四類SQL語句

我親愛的對手&#xff0c;亦敵亦友&#xff0c;但我同樣希望你能成功&#xff0c;與我一起&#xff0c;站在人生的山頂上 ——24.3.1 一、DDL 數據定義語言 1.DDL —— 數據庫操作 查詢 查詢所有數據庫 show databases; 查詢當前數據庫 select database(); 創建 create databa…

【簡說八股】Nginx、GateWay、Ribbon有什么區別?

前言 在現代的微服務架構中&#xff0c;Nginx、Gateway 和 Ribbon 都是處理網絡請求和服務的組件&#xff0c;但它們各自扮演的角色和提供的功能有所不同。下面我將詳細解釋它們之間的區別&#xff1a; Nginx Nginx 是一個高性能的 HTTP 和反向代理服務器&#xff0c;它也可…

Golang Vs Java:為您的下一個項目選擇正確的工具

Java 首次出現在 1995 年&#xff0c;由 James Gosling 和 Sun Microsystems 的其他人開發的一種新編程語言。從那時起&#xff0c;Java 已成為世界上最受歡迎和廣泛使用的編程語言之一。Java 的主要特點包括其面向對象的設計、健壯性、平臺獨立性、自動內存管理以及廣泛的內置…

MSMFN

CDFI是彩色多普勒血流成像 輔助信息 作者未提供數據

Codeforces Round 930 (Div. 2)

substr時間復雜度O&#xff08;N&#xff09;&#xff0c;不能一遍遍找&#xff0c;會超時 #include<iostream> #include<algorithm> #include<vector> #include<map> using namespace std; const int N5e510; map<string,int>mp; vector<…

[C++]AVL樹怎么轉

AVL樹是啥 一提到AVL樹&#xff0c;腦子里不是旋了&#xff0c;就是懸了。 AVL樹之所以難&#xff0c;并不是因為結構難以理解&#xff0c;而是因為他的旋轉。 AVL樹定義 平衡因子&#xff1a;對于一顆二叉樹&#xff0c;某節點的左右子樹高度之差&#xff0c;就是該節點的…

5、云原生安全之falco的規則解讀(部分)(上)

文章目錄 1、自定義規則測試1.1、自定義檢測定時任務的規則2、自帶規則詳解部分2.1、意外的出站連接源(類似的還有入站連接)2.2、檢測目錄穿越攻擊2.3、rpm數據庫被修改2.4、數據庫派生新的進程2.5、特權容器啟動2.6、啟動容器掛載到敏感路徑2.7、匹配所有在pod內啟動、并連接…

音視頻數字化(數字與模擬-照相機)

目錄 1、模擬/數字 2、第一臺照相機 3、照相機原理 4、取景方式 5、底片 6、數碼相機 7、數碼相機指標 8、數碼相機分類 (1)單反相機 (2)單電相機 (3)無反相機

2024.03.02藍橋云課筆記

1.scanf與printf取消分隔符的限制方法 示例代碼&#xff1a; int main() { char s[10];scanf("%d[^\n]",s);printf("%s",s);return 0; } 運行&#xff1a; 輸入&#xff1a;Hello World 輸出&#xff1a;Hello World 注&#xff1a;其中[]中是一個正則…

(UE4升級UE5)Selected Level Actor節點升級到UE5

本問所用工具為&#xff1a;AssetDeveTool虛幻開發常用工具https://gf.bilibili.com/item/detail/1104960041 在UE4中 編輯器藍圖有個節點為 Get Selected Level Actors 但在UE5中&#xff0c;藍圖直接升級后&#xff0c;節點失效&#xff0c;如圖&#xff1a; 因為在UE5中&am…

Vue3中Vuex狀態管理庫學習筆記

1.什么是狀態管理 在開發中&#xff0c;我們會的應用程序需要處理各種各樣的數據&#xff0c;這些數據需要保存在我們應用程序的某個位置&#xff0c;對于這些數據的管理我們就稱之為狀態管理。 在之前我們如何管理自己的狀態呢&#xff1f; 在Vue開發中&#xff0c;我們使用…

大廠面試經驗:如何對加密后的數據進行模糊查詢操作

加密后的數據對模糊查詢不是很友好&#xff0c;本篇就針對加密數據模糊查詢這個問題來展開講一講實現的思路。 為了數據安全我們在開發過程中經常會對重要的數據進行加密存儲&#xff0c;常見的有&#xff1a;密碼、手機號、電話號碼、詳細地址、銀行卡號、信用卡驗證碼等信息…

YoloV5改進策略:主干網絡改進|MogaNet——高效的多階門控聚合網絡

文章目錄 摘要論文:《MogaNet——高效的多階門控聚合網絡》1、簡介2、相關工作2.1、視覺Transformers2.2、ViT時代的卷積網絡3、從多階博弈論交互的角度看表示瓶頸4、方法論4.1、MogaNet概述4.2、多階門控聚合4.3、通過通道聚合進行多階特征重新分配4.4、實現細節5、實驗5.1、…

Vue 3 中的 setup 函數是如何工作的?

Vue 3 中的 setup 函數是一個新的組件選項&#xff0c;用于使用組合式 API 定義組件的邏輯。這個函數的引入是為了解決 Vue 2 中隨著組件復雜度的增長&#xff0c;選項式的 API 可能導致代碼難以維護和理解的問題。通過 setup 函數&#xff0c;開發者可以更加靈活地組織和共享代…

Python光速入門 - Flask輕量級框架

FlASK是一個輕量級的WSGI Web應用程序框架&#xff0c;Flask的核心包括Werkzeug工具箱和Jinja2模板引擎&#xff0c;它沒有默認使用的數據庫或窗體驗證工具&#xff0c;這意味著用戶可以根據自己的需求選擇不同的數據庫和驗證工具。Flask的設計理念是保持核心簡單&#xff0c…

布隆過濾器實戰

一、背景 本篇文章以解決實際需求的問題的角度進行切入&#xff0c;探討了如果使用布隆過濾器快速丟棄無效請求&#xff0c;降低了系統的負載以及不必要的流量。 我們都知道布隆過濾器是以占用內存小&#xff0c;同時也能夠實現快速的過濾從而滿足我們的需求&#xff0c;本篇…

Matlab偏微分方程擬合 | 源碼分享 | 視頻教程

專欄導讀 作者簡介&#xff1a;工學博士&#xff0c;高級工程師&#xff0c;專注于工業軟件算法研究本文已收錄于專欄&#xff1a;《復雜函數擬合案例分享》本專欄旨在提供 1.以案例的形式講解各類復雜函數擬合的程序實現方法&#xff0c;并提供所有案例完整源碼&#xff1b;2.…

反編譯代碼格式處理

反編譯代碼格式處理 背景解決方案程序跑之后idea格式化 總結 背景 想看看公司里一個工具的代碼實現&#xff0c;手里只有一個jar包&#xff0c;只能通過jd-gui反編譯代碼。但是呢&#xff0c;源碼是有了&#xff0c;但是看的很難受。 解決方案 /*** 替換 {code searchDir}中…

LeetCode 100231.超過閾值的最少操作數 I

給你一個下標從 0 開始的整數數組 nums 和一個整數 k 。 一次操作中&#xff0c;你可以刪除 nums 中的最小元素。 你需要使數組中的所有元素都大于或等于 k &#xff0c;請你返回需要的 最少 操作次數。 示例 1&#xff1a; 輸入&#xff1a;nums [2,11,10,1,3], k 10 輸…

Linux課程四課---Linux開發環境的使用(自動化構建工具-make/Makefile的相關)

作者前言 &#x1f382; ??????&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ?&#x1f382; 作者介紹&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…