日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JavaScript 发布-订阅模式

發布時間:2024/4/14 javascript 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript 发布-订阅模式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

發布-訂閱模式,看似陌生,其實不然。工作中經常會用到,例如 Node.js EventEmitter 中的 on 和 emit 方法;Vue 中的 $on 和 $emit 方法。他們都使用了發布-訂閱模式,讓開發變得更加高效方便。

一、 什么是發布-訂閱模式

1. 定義

發布-訂閱模式其實是一種對象間一對多的依賴關系,當一個對象的狀態發送改變時,所有依賴于它的對象都將得到狀態改變的通知。

訂閱者(Subscriber)把自己想訂閱的事件注冊(Subscribe)到調度中心(Event Channel),當發布者(Publisher)發布該事件(Publish Event)到調度中心,也就是該事件觸發時,由調度中心統一調度(Fire Event)訂閱者注冊到調度中心的處理代碼。

2. 例子

比如我們很喜歡看某個公眾號號的文章,但是我們不知道什么時候發布新文章,要不定時的去翻閱;這時候,我們可以關注該公眾號,當有文章推送時,會有消息及時通知我們文章更新了。

上面一個看似簡單的操作,其實是一個典型的發布訂閱模式,公眾號屬于發布者,用戶屬于訂閱者;用戶將訂閱公眾號的事件注冊到調度中心,公眾號作為發布者,當有新文章發布時,公眾號發布該事件到調度中心,調度中心會及時發消息告知用戶。

二、 如何實現發布-訂閱模式?

1. 實現思路

  • 創建一個對象
  • 在該對象上創建一個緩存列表(調度中心)
  • on 方法用來把函數 fn 都加到緩存列表中(訂閱者注冊事件到調度中心)
  • emit 方法取到 arguments 里第一個當做 event,根據 event 值去執行對應緩存列表中的函數(發布者發布事件到調度中心,調度中心處理代碼)
  • off 方法可以根據 event 值取消訂閱(取消訂閱)
  • once 方法只監聽一次,調用完畢后刪除緩存函數(訂閱一次)

2. demo1

我們來看個簡單的 demo,實現了 on 和 emit 方法,代碼中有詳細注釋。

// 公眾號對象 let eventEmitter = {};// 緩存列表,存放 event 及 fn eventEmitter.list = {};// 訂閱 eventEmitter.on = function (event, fn) {let _this = this;// 如果對象中沒有對應的 event 值,也就是說明沒有訂閱過,就給 event 創建個緩存列表// 如有對象中有相應的 event 值,把 fn 添加到對應 event 的緩存列表里(_this.list[event] || (_this.list[event] = [])).push(fn);return _this; };// 發布 eventEmitter.emit = function () {let _this = this;// 第一個參數是對應的 event 值,直接用數組的 shift 方法取出let event = [].shift.call(arguments),fns = _this.list[event];// 如果緩存列表里沒有 fn 就返回 falseif (!fns || fns.length === 0) {return false;}// 遍歷 event 值對應的緩存列表,依次執行 fnfns.forEach(fn => {fn.apply(_this, arguments);});return _this; };function user1 (content) {console.log('用戶1訂閱了:', content); };function user2 (content) {console.log('用戶2訂閱了:', content); };// 訂閱 eventEmitter.on('article', user1); eventEmitter.on('article', user2);// 發布 eventEmitter.emit('article', 'Javascript 發布-訂閱模式');/*用戶1訂閱了: Javascript 發布-訂閱模式用戶2訂閱了: Javascript 發布-訂閱模式 */復制代碼

3. demo2

這一版中我們補充了一下 once 和 off 方法。

let eventEmitter = {// 緩存列表list: {},// 訂閱on (event, fn) {let _this = this;// 如果對象中沒有對應的 event 值,也就是說明沒有訂閱過,就給 event 創建個緩存列表// 如有對象中有相應的 event 值,把 fn 添加到對應 event 的緩存列表里(_this.list[event] || (_this.list[event] = [])).push(fn);return _this;},// 監聽一次once (event, fn) {// 先綁定,調用后刪除let _this = this;function on () {_this.off(event, on);fn.apply(_this, arguments);}on.fn = fn;_this.on(event, on);return _this;},// 取消訂閱off (event, fn) {let _this = this;let fns = _this.list[event];// 如果緩存列表中沒有相應的 fn,返回falseif (!fns) return false;if (!fn) {// 如果沒有傳 fn 的話,就會將 event 值對應緩存列表中的 fn 都清空fns && (fns.length = 0);} else {// 若有 fn,遍歷緩存列表,看看傳入的 fn 與哪個函數相同,如果相同就直接從緩存列表中刪掉即可let cb;for (let i = 0, cbLen = fns.length; i < cbLen; i++) {cb = fns[i];if (cb === fn || cb.fn === fn) {fns.splice(i, 1);break}}}return _this;},// 發布emit () {let _this = this;// 第一個參數是對應的 event 值,直接用數組的 shift 方法取出let event = [].shift.call(arguments),fns = _this.list[event];// 如果緩存列表里沒有 fn 就返回 falseif (!fns || fns.length === 0) {return false;}// 遍歷 event 值對應的緩存列表,依次執行 fnfns.forEach(fn => {fn.apply(_this, arguments);});return _this;} };function user1 (content) {console.log('用戶1訂閱了:', content); }function user2 (content) {console.log('用戶2訂閱了:', content); }function user3 (content) {console.log('用戶3訂閱了:', content); }function user4 (content) {console.log('用戶4訂閱了:', content); }// 訂閱 eventEmitter.on('article1', user1); eventEmitter.on('article1', user2); eventEmitter.on('article1', user3);// 取消user2方法的訂閱 eventEmitter.off('article1', user2);eventEmitter.once('article2', user4)// 發布 eventEmitter.emit('article1', 'Javascript 發布-訂閱模式'); eventEmitter.emit('article1', 'Javascript 發布-訂閱模式'); eventEmitter.emit('article2', 'Javascript 觀察者模式'); eventEmitter.emit('article2', 'Javascript 觀察者模式');// eventEmitter.on('article1', user3).emit('article1', 'test111');/*用戶1訂閱了: Javascript 發布-訂閱模式用戶3訂閱了: Javascript 發布-訂閱模式用戶1訂閱了: Javascript 發布-訂閱模式用戶3訂閱了: Javascript 發布-訂閱模式用戶4訂閱了: Javascript 觀察者模式 */復制代碼

三、 Vue 中的實現

有了發布-訂閱模式的知識后,我們來看下 Vue 中怎么實現 $on 和 $emit 的方法,直接看源碼:

function eventsMixin (Vue) {var hookRE = /^hook:/;Vue.prototype.$on = function (event, fn) {var this$1 = this;var vm = this;// event 為數組時,循環執行 $onif (Array.isArray(event)) {for (var i = 0, l = event.length; i < l; i++) {this$1.$on(event[i], fn);}} else {(vm._events[event] || (vm._events[event] = [])).push(fn);// optimize hook:event cost by using a boolean flag marked at registration // instead of a hash lookupif (hookRE.test(event)) {vm._hasHookEvent = true;}}return vm};Vue.prototype.$once = function (event, fn) {var vm = this;// 先綁定,后刪除function on () {vm.$off(event, on);fn.apply(vm, arguments);}on.fn = fn;vm.$on(event, on);return vm};Vue.prototype.$off = function (event, fn) {var this$1 = this;var vm = this;// all,若沒有傳參數,清空所有訂閱if (!arguments.length) {vm._events = Object.create(null);return vm}// array of events,events 為數組時,循環執行 $offif (Array.isArray(event)) {for (var i = 0, l = event.length; i < l; i++) {this$1.$off(event[i], fn);}return vm}// specific eventvar cbs = vm._events[event];if (!cbs) {// 沒有 cbs 直接 return thisreturn vm}if (!fn) {// 若沒有 handler,清空 event 對應的緩存列表vm._events[event] = null;return vm}if (fn) {// specific handler,刪除相應的 handlervar cb;var i$1 = cbs.length;while (i$1--) {cb = cbs[i$1];if (cb === fn || cb.fn === fn) {cbs.splice(i$1, 1);break}}}return vm};Vue.prototype.$emit = function (event) {var vm = this;{// 傳入的 event 區分大小寫,若不一致,有提示var lowerCaseEvent = event.toLowerCase();if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {tip("Event \"" + lowerCaseEvent + "\" is emitted in component " +(formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " +"Note that HTML attributes are case-insensitive and you cannot use " +"v-on to listen to camelCase events when using in-DOM templates. " +"You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\".");}}var cbs = vm._events[event];if (cbs) {cbs = cbs.length > 1 ? toArray(cbs) : cbs;// 只取回調函數,不取 eventvar args = toArray(arguments, 1);for (var i = 0, l = cbs.length; i < l; i++) {try {cbs[i].apply(vm, args);} catch (e) {handleError(e, vm, ("event handler for \"" + event + "\""));}}}return vm}; }/**** Convert an Array-like object to a real Array.*/ function toArray (list, start) {start = start || 0;var i = list.length - start;var ret = new Array(i);while (i--) {ret[i] = list[i + start];}return ret } 復制代碼

實現思路大體相同,如上第二點中的第一條:實現思路。Vue 中實現的方法支持訂閱數組事件。

四、 總結

1. 優點

  • 對象之間解耦
  • 異步編程中,可以更松耦合的代碼編寫

2. 缺點

  • 創建訂閱者本身要消耗一定的時間和內存
  • 雖然可以弱化對象之間的聯系,多個發布者和訂閱者嵌套一起的時候,程序難以跟蹤維護

五、 擴展(發布-訂閱模式與觀察者模式的區別)

很多地方都說發布-訂閱模式是觀察者模式的別名,但是他們真的一樣嗎?是不一樣的。

直接上圖:

觀察者模式:觀察者(Observer)直接訂閱(Subscribe)主題(Subject),而當主題被激活的時候,會觸發(Fire Event)觀察者里的事件。

發布訂閱模式:訂閱者(Subscriber)把自己想訂閱的事件注冊(Subscribe)到調度中心(Event Channel),當發布者(Publisher)發布該事件(Publish Event)到調度中心,也就是該事件觸發時,由調度中心統一調度(Fire Event)訂閱者注冊到調度中心的處理代碼。

差異

  • 在觀察者模式中,觀察者是知道 Subject 的,Subject 一直保持對觀察者進行記錄。然而,在發布訂閱模式中,發布者和訂閱者不知道對方的存在。它們只有通過消息代理進行通信。

  • 在發布訂閱模式中,組件是松散耦合的,正好和觀察者模式相反。

  • 觀察者模式大多數時候是同步的,比如當事件觸發,Subject 就會去調用觀察者的方法。而發布-訂閱模式大多數時候是異步的(使用消息隊列)。

  • 觀察者模式需要在單個應用程序地址空間中實現,而發布-訂閱更像交叉應用模式。

轉載于:https://juejin.im/post/5ce75fe16fb9a07eb67d6999

總結

以上是生活随笔為你收集整理的JavaScript 发布-订阅模式的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。