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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JavaScript 设计模式之观察者模式与发布订阅模式

發(fā)布時(shí)間:2025/3/21 javascript 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript 设计模式之观察者模式与发布订阅模式 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

在軟體工程中,設(shè)計(jì)模式(design pattern)是對軟體設(shè)計(jì)中普遍存在(反復(fù)出現(xiàn))的各種問題,所提出的解決方案。

設(shè)計(jì)模式并不直接用來完成程式碼的編寫,而是描述在各種不同情況下,要怎么解決問題的一種方案。

設(shè)計(jì)模式能使不穩(wěn)定轉(zhuǎn)為相對穩(wěn)定、具體轉(zhuǎn)為相對抽象,避免會(huì)引起麻煩的緊耦合,以增強(qiáng)軟體設(shè)計(jì)面對并適應(yīng)變化的能力

——維基百科

設(shè)計(jì)模式是一種軟件開發(fā)的思想,有益于降低代碼的耦合性,增強(qiáng)代碼的健壯性。往往在大型項(xiàng)目中用的比較多。

今天就來介紹一下觀察者模式與發(fā)布訂閱模式。這在解耦中非常實(shí)用。

什么是觀察者模式?

先舉一個(gè)簡單的例子

畢業(yè)前,很多同學(xué)都會(huì)說類似于這樣的話:

“老王,等你結(jié)婚了,記得叫我來喝喜酒!”

于是有一天你真的要結(jié)婚了,且需要舉辦酒席,這時(shí)候你需要通知你的你的那些老友來喝喜酒。于是你拿起了手機(jī)給你的那些分布于世界各地的好朋友打起了電話,說了結(jié)婚酒席一事。

到了酒席那天,有的朋友來了,有的人沒來禮卻到了,有的呢只有簡短的兩句祝福,剩下的只有推脫。

這就是觀察者模式

在觀察者模式中,目標(biāo)與觀察者相互獨(dú)立,又相互聯(lián)系:

  • 兩者都是相互獨(dú)立的對對象個(gè)體。
  • 觀察者在目標(biāo)對象中訂閱事件,目標(biāo)廣播發(fā)布事件。

就像之前的例子一樣:

  • 老王就是模式中所謂的目標(biāo)。
  • 同學(xué)們在畢業(yè)前說的話就相當(dāng)于在目標(biāo)對象上訂閱事件。
  • 老王打電話通知朋友就是發(fā)布事件。
  • 同學(xué)們各自作出了不同的行動(dòng)回應(yīng)。

這么說我們的代碼就慢慢建立起來了。

首先我們需要定義兩個(gè)對象:

  • 目標(biāo)對象:Subject
  • 觀察者對象:Observer
  • 并且在目標(biāo)對象中要存放觀察者對象的引用,就像老王要存放同學(xué)的手機(jī)好一樣,只有存了才能聯(lián)系嘛。于是我們有了下面的代碼:

    function Subject() {this.observers = new ObserverList(); } function ObserverList() {this.observerList = []; } function Observer() {} 復(fù)制代碼

    對于目標(biāo)對象中的引用,我們必須可以動(dòng)態(tài)的控制:

    ObserverList.prototype.add = function(obj) {return this.observerList.push(obj); };ObserverList.prototype.count = function() {return this.observerList.length; };ObserverList.prototype.get = function(index) {if (index > -1 && index < this.observerList.length) {return this.observerList[index];} };ObserverList.prototype.indexOf = function(obj, startIndex) {var i = startIndex;while (i < this.observerList.length) {if (this.observerList[i] === obj) {return i;}i++;}return -1; };ObserverList.prototype.removeAt = function(index) {this.observerList.splice(index, 1); };Subject.prototype.addObserver = function(observer) {this.observers.add(observer); };Subject.prototype.removeObserver = function(observer) {this.observers.removeAt(this.observers.indexOf(observer, 0)); }; 復(fù)制代碼

    這樣我們就能對老王手機(jī)聯(lián)系人進(jìn)行增、刪、查的操作了。

    現(xiàn)在我們就要考慮發(fā)布消息的功能函數(shù)了。首先必須明確一點(diǎn):目標(biāo)對象并不能指定觀察者對象做出什么相應(yīng)的變化。目標(biāo)對象只有通知的作用。就像老王只能告訴朋友他要辦喜酒了,至于朋友接下來怎么辦,則全是朋友自己決定的。

    所以我們得寫一個(gè)目標(biāo)廣播消息的功能函數(shù):

    Subject.prototype.notify = function(context) {var observerCount = this.observers.count();for (var i = 0; i < observerCount; i++) {this.observers.get(i).update(context);} }; 復(fù)制代碼

    我們將具體的觀察者對象該作出的變化交給了觀察者對象自己去處理。這就要求觀察者對象需要擁有自己的 update(context)方法來作出改變,同時(shí)該方法不應(yīng)該寫在原型鏈上,因?yàn)槊恳粋€(gè)實(shí)例化后的 Observer 對象所做的響應(yīng)都是不同的,需要獨(dú)立存儲(chǔ) update(context)方法:

    function Observer() {this.update = function() {// ...}; } 復(fù)制代碼

    到此我們就完成了一個(gè)簡單的觀察者模式的構(gòu)建。

    完整代碼:

    function ObserverList() {this.observerList = []; }ObserverList.prototype.add = function(obj) {return this.observerList.push(obj); };ObserverList.prototype.count = function() {return this.observerList.length; };ObserverList.prototype.get = function(index) {if (index > -1 && index < this.observerList.length) {return this.observerList[index];} };ObserverList.prototype.indexOf = function(obj, startIndex) {var i = startIndex;while (i < this.observerList.length) {if (this.observerList[i] === obj) {return i;}i++;}return -1; };ObserverList.prototype.removeAt = function(index) {this.observerList.splice(index, 1); };function Subject() {this.observers = new ObserverList(); }Subject.prototype.addObserver = function(observer) {this.observers.add(observer); };Subject.prototype.removeObserver = function(observer) {this.observers.removeAt(this.observers.indexOf(observer, 0)); };Subject.prototype.notify = function(context) {var observerCount = this.observers.count();for (var i = 0; i < observerCount; i++) {this.observers.get(i).update(context);} };// The Observer function Observer() {this.update = function() {// ...}; } 復(fù)制代碼

    什么是發(fā)布訂閱模式?

    先舉個(gè)簡單的例子:

    我們生活中,特別是在一線城市打拼的年輕人,與租房的聯(lián)系再密切不過了。同時(shí)我們的身邊也有很多租房中介。

    某天路人甲需要租一套三室一廳一廚一衛(wèi)的房,他找到了中介問了問有沒有。中介看了看發(fā)現(xiàn)并沒有他要的房型,于是和路人甲說:“等有房東提供了此類房型的時(shí)候再聯(lián)系你。”于是你就回去等消息了。

    有一天,某一位房東將自己多余的房屋信息以及圖片整理好發(fā)給中介,中介看了看,這不就是路人甲要的房型嗎。于是立馬打電話讓路人甲看房。最終撮合了一單生意。

    這就是發(fā)布訂閱模式

    可以看出,在發(fā)布訂閱模式中最重要的是 Topic/Event Channel (Event)對象。我們可以簡單的稱之為“中介”。

    在這個(gè)中介對象中既要接受發(fā)布者所發(fā)布的消息,又要將消息派發(fā)給訂閱者。所以中介還應(yīng)該按照不同的事件儲(chǔ)存相應(yīng)的訂閱者信息。

    首先我們先會(huì)給中介對象的每個(gè)訂閱者對象一個(gè)標(biāo)識(shí),每當(dāng)有一個(gè)新的訂閱者訂閱事件的時(shí)候,我們就給一個(gè) subUid。

    我們先來寫一下中介對象(pubsub):

    var pubsub = {}; (function(myObject) {var topics = {};var subUid = -1;myObject.publish = function() {};myObject.subscribe = function() {};myObject.unsubscribe = function() {}; })(pubsub); 復(fù)制代碼

    這里我們用了工廠模式來創(chuàng)建我們的中介對象。

    我們先把訂閱功能實(shí)現(xiàn):

    首先我們必須認(rèn)識(shí)到 topics 對象將存放著如下類型的數(shù)據(jù):

    topics = {topicA: [{token: subuid,function: func},...],topicB: [{token: subuid,function: func},...],... } 復(fù)制代碼

    對于 topics 對象,存放在許多不同的事件名稱(topicA...),對于每一個(gè)事件都有指定的一個(gè)數(shù)組對象用以存放訂閱該事件的訂閱對象及發(fā)生事件之后作出的響應(yīng)。

    所以當(dāng)有訂閱對象在中介中訂閱事件時(shí):

    myObject.subscribe = function(topic, func) {//如果不存在相應(yīng)事件就創(chuàng)建一個(gè)if (!topics[topic]) {topics[topic] = [];}//將訂閱對象信息記錄下來var token = (++subUid).toString();topics[topic].push({token: token,func: func});//返回訂閱者標(biāo)識(shí),方標(biāo)在取消訂閱的時(shí)候使用return token; }; 復(fù)制代碼

    接下來我們來實(shí)現(xiàn)取消訂閱的功能:

    我們只需要遍歷 topics 各個(gè)事件中的對象即可。

    myObject.unsubscribe = function(token) {for (var m in topics) {if (topics[m]) {for (var i = 0, j = topics[m].length; i < j; i++) {if (topics[m][i].token === token) {topics[m].splice(i, 1);return token;}}}}return this; }; 復(fù)制代碼

    剩下的就是發(fā)布事件的實(shí)現(xiàn)了:

    我們只需要給定事件名稱 topic 和相應(yīng)的參數(shù)即可,找到相應(yīng)事件所對應(yīng)的訂閱者列表,遍歷調(diào)用列表中的方法。

    myObject.publish = function(topic, args) {if (!topics[topic]) {return false;}var subscribers = topics[topic],len = subscribers ? subscribers.length : 0;while (len--) {subscribers[len].func(args);}return this; }; 復(fù)制代碼

    至此,我們的中介對象就完成了。在發(fā)布訂閱模式中我們不必在意發(fā)布者和訂閱者。

    完整代碼:

    var pubsub = {};(function(myObject) {var topics = {};var subUid = -1;myObject.publish = function(topic, args) {if (!topics[topic]) {return false;}var subscribers = topics[topic],len = subscribers ? subscribers.length : 0;while (len--) {subscribers[len].func(args);}return this;};myObject.subscribe = function(topic, func) {if (!topics[topic]) {topics[topic] = [];}var token = (++subUid).toString();topics[topic].push({token: token,func: func});return token;};myObject.unsubscribe = function(token) {for (var m in topics) {if (topics[m]) {for (var i = 0, j = topics[m].length; i < j; i++) {if (topics[m][i].token === token) {topics[m].splice(i, 1);return token;}}}}return this;}; })(pubsub); 復(fù)制代碼

    二者的區(qū)別和聯(lián)系

    區(qū)別:

  • 觀察者模式中需要觀察者對象自己定義事件發(fā)生時(shí)的相應(yīng)方法。
  • 發(fā)布訂閱模式者在發(fā)布對象和訂閱對象之中加了一個(gè)中介對象。我們不需要在乎發(fā)布者對象和訂閱者對象的內(nèi)部是什么,具體響應(yīng)時(shí)間細(xì)節(jié)全部由中介對象實(shí)現(xiàn)。
  • 聯(lián)系:

  • 二者都降低了代碼的耦合性。
  • 都具有消息傳遞的機(jī)制,以數(shù)據(jù)為中心的設(shè)計(jì)思想。
  • 實(shí)戰(zhàn)

    這里需要一點(diǎn)模板引擎的知識(shí),關(guān)于模板引擎可以看我之前發(fā)的一篇文章:《手?jǐn)] JavaScript 模板引擎》

    假如我們有如下模板需要渲染:

    var template = `<span><% this.value %></span>`; 復(fù)制代碼

    該模板依賴的數(shù)據(jù)源如下:

    var data = {value: 0 }; 復(fù)制代碼

    現(xiàn)假若 data 中的 value 時(shí)動(dòng)態(tài)的,每隔一秒加 1。

    setInterval(function() {data.value++; }, 1000); 復(fù)制代碼

    同時(shí)我們也要在頁面上發(fā)生變化,這時(shí)你可能寫出如下代碼:

    setInterval(function() {data.value++;document.body.innerHTML = TemplateEngine(template, data); }, 1000); 復(fù)制代碼

    我們可以對比一下發(fā)布訂閱模式的實(shí)現(xiàn):

    var template = `<span><% this.value %></span>`; var data = {value: 0 }; function render() {document.body.innerHTML = TemplateEngine(template, data); } window.onload = function() {render();pubsub.subscribe("change", render);setInterval(function() {data.value++;pubsub.publish("change");}, 1000); }; 復(fù)制代碼

    前者似乎看起來很簡單明了,但是:

  • 不同功能緊密耦合,如果以后要修改該功能,很可能牽一發(fā)而動(dòng)全身。
  • 往往實(shí)際開發(fā)中我們的訂閱者不止一個(gè),發(fā)布者的消息也不止一個(gè),遠(yuǎn)遠(yuǎn)比這個(gè)例子的邏輯復(fù)雜的多。剪不斷,理還亂。
  • 相比之下,發(fā)布訂閱模式就顯得邏輯清晰,已于維護(hù),值得細(xì)細(xì)體味。

    值得一提:事件監(jiān)聽的實(shí)現(xiàn)

    事件監(jiān)聽是我們經(jīng)常用到的功能,其實(shí)它的實(shí)現(xiàn)就是源自于發(fā)布訂閱模式,不信你看:

    subject.addEventListener("click", () => {//... }); 復(fù)制代碼

    這就是在訂閱一個(gè)事件的調(diào)用。

    其實(shí)觀察者模式與發(fā)布訂閱模式與我們息息相關(guān)!?

    -EFO-


    筆者專門在 github 上創(chuàng)建了一個(gè)倉庫,用于記錄平時(shí)學(xué)習(xí)全棧開發(fā)中的技巧、難點(diǎn)、易錯(cuò)點(diǎn),歡迎大家點(diǎn)擊下方鏈接瀏覽。如果覺得還不錯(cuò),就請給個(gè)小星星吧!?


    2019/04/28

    AJie

    轉(zhuǎn)載于:https://juejin.im/post/5cc57704e51d456e5a072975

    總結(jié)

    以上是生活随笔為你收集整理的JavaScript 设计模式之观察者模式与发布订阅模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。