摘要:觀察者模式,是設(shè)計(jì)模式之一。即便如此,筆者仍精心準(zhǔn)備了這篇博客,期望用最簡(jiǎn)單的方式來(lái)介紹下該模式。這里有個(gè)小知識(shí)點(diǎn)提一下函數(shù)對(duì)象的屬性就是該函數(shù)名最后是思路是通過(guò)找到指定數(shù)組,然后對(duì)數(shù)組中的回調(diào)函數(shù)進(jìn)行依次調(diào)用,達(dá)到發(fā)布的目的。
觀察者模式,是JavaScript設(shè)計(jì)模式之一。當(dāng)然也不僅僅限于JavaScript這門(mén)語(yǔ)言,網(wǎng)上對(duì)該模式的介紹已是多如牛毛,而且講得各有特色各有心得。即便如此,筆者仍精心準(zhǔn)備了這篇博客,期望用最簡(jiǎn)單的方式來(lái)介紹下該模式。
首先來(lái)看下維基百科對(duì) 觀察者模式 的解釋:
觀察者模式是軟件設(shè)計(jì)模式的一種。在此種模式中,一個(gè)目標(biāo)對(duì)象管理所有相依于它的觀察者對(duì)象,并且在它本身的狀態(tài)改變時(shí)主動(dòng)發(fā)出通知。這通常透過(guò)呼叫各觀察者所提供的方法來(lái)實(shí)現(xiàn)。此種模式通常被用來(lái)實(shí)時(shí)事件處理系統(tǒng)。
其實(shí)筆者更傾向于它的另一個(gè)名字發(fā)布/訂閱模式(Publish/Subscribe),因?yàn)楦鼙磉_(dá)出該模式的核心思路,那就是:發(fā)布和訂閱兩個(gè)過(guò)程。是不是還感覺(jué)模棱兩可?不用擔(dān)心,下面就用咱們身邊發(fā)生的事情來(lái)做個(gè)形象化的解釋:
大家都有訂閱網(wǎng)站郵件的經(jīng)歷吧?如果你沒(méi)有的話,emmmmm....那就繼續(xù)往下看吧哈哈!!
假如我今天想訂閱xxx公司的郵件,那么這里就涉及到兩個(gè)對(duì)象:我和xxx公司,
從行為上來(lái)看就是我訂閱了xxx公司郵件,xxx公司會(huì)發(fā)送郵件給我的郵箱。但某天我不想再收到xxx公司的郵件了,那么我可以取消訂閱,這樣xxx公司就不會(huì)再發(fā)郵件到我的郵箱。
說(shuō)到這里,是不是就有點(diǎn)眉頭了呢?好,我們繼續(xù)往下說(shuō),
通過(guò)剛剛的形象化解釋,我們可以羅列下觀察者模式的一些核心的東西:
對(duì)象:我(訂閱者), xxx公司(發(fā)布者), 可以直接對(duì)應(yīng) 發(fā)布/訂閱模式(Publish/Subscribe)
行為:訂閱、發(fā)送和取消訂閱
說(shuō)不如做,下面開(kāi)始用代碼來(lái)更直觀的描繪下觀察者模式吧。
首先我們定義一個(gè)發(fā)布者 (相當(dāng)于xxx公司)
let publisher = { }
那么一起來(lái)按照訂閱郵件的過(guò)程想象下,發(fā)布者具有那些屬性或者方法?
首先,我們訂閱一個(gè)xxx網(wǎng)站的郵件,是不是需要xxx網(wǎng)站給我們提供訂閱入口?那么publisher中必定會(huì)有一個(gè)方法提供給我們實(shí)現(xiàn)訂閱。
其次,如果xxx公司要向訂閱者們發(fā)送自己的郵件,是不是需要一個(gè)方法去做?那么publisher中必定會(huì)有一個(gè)方法提供給我們實(shí)現(xiàn)發(fā)送或者說(shuō)說(shuō)發(fā)布。
再然后,如例子所說(shuō)如果我突然不想訂閱xxx公司的郵件了,xxx公司 就得提供給我一個(gè)取消訂閱的入口,那么publisher中必定會(huì)有一個(gè)方法提供給我們實(shí)現(xiàn)取消訂閱。
最后,如果我們訂閱了xxx公司的郵件,那么他就得記錄我訂閱所用的郵箱地址吧,所以publisher中必定會(huì)有一個(gè)“注冊(cè)表”來(lái)存儲(chǔ)訂閱的對(duì)象,也就是說(shuō)我們的 郵箱地址。
說(shuō)到這里一切都了然了,下面還是講想象到的東西用代碼表達(dá)出來(lái)吧
let publisher = { registration: {}, subscribe: function (type, fn) {}, unSubscribe: function (type, fnName) {}, publish: function (type, message) {} }
簡(jiǎn)單解釋下,
registration就是上面提到的注冊(cè)表,至于為什么把它設(shè)計(jì)成一個(gè)對(duì)象是因?yàn)榭紤]到xxx公司可能有更多類型的郵件,比如 游戲,金融,投資理財(cái)?shù)鹊龋跃桶阉O(shè)計(jì)成對(duì)象以key-value的形式存儲(chǔ)訂閱者, 比如:{"game":[],"monetary":[]}該形式
subscribe 則是publisher提供給我們的對(duì)其進(jìn)行訂閱的方法,參數(shù)是type和 fn。type就是郵件的類型,fn就是我們提供給publisher用于通知我的渠道 (郵箱)。在JavaScript中更多的是回調(diào)函數(shù)。
unSubscribe是publisher提供給我們的對(duì)其進(jìn)行取消訂閱的方法,參數(shù)是type和 fnName。type就不多說(shuō)了,fnName則是我們提供給publisher用于取消訂閱的標(biāo)志,比如說(shuō)郵箱,或者是回調(diào)函數(shù)的名字等等。
publish說(shuō)到比較重要的方法,這就是publisher向所有訂閱者發(fā)布消息的方法。
下面開(kāi)始一步一步得實(shí)現(xiàn)三個(gè)方法,registration保持不變:
首先是subscribe
subscribe: function (type, fn) { if (Object.keys(this.registration).indexOf(type) >= 0) { this.registration[type].push(fn); } else { this.registration[type] = []; this.registration[type].push(fn); } }
這里的思路是將 Callback Function 存儲(chǔ)到registration對(duì)于類型的數(shù)組中,以待publish調(diào)用。
然后是 unSubscribe
unSubscribe: function (type, fnName) { if (Object.keys(this.registration).indexOf(type) >= 0) { let index = -1; this.registration[type].forEach(function (func, idx) { if (func.name === fnName) { index = idx; } }) index > -1 ? this.registration[type].splice(index, 1) : null } }
思路是首先通過(guò) type 確定數(shù)組對(duì)象,然后通過(guò)方法對(duì)象的名字進(jìn)行判斷,最后直接剔除操作。
** 這里有個(gè)小知識(shí)點(diǎn)提一下:函數(shù)對(duì)象的name屬性就是該函數(shù)名 **
最后是 publish
publish: function (type, message) { if (Object.keys(this.registration).indexOf(type) >= 0) { for (let fn of this.registration[type]) { fn(message) } } }
思路是通過(guò) type 找到指定數(shù)組,然后對(duì)數(shù)組中的回調(diào)函數(shù)進(jìn)行依次調(diào)用,達(dá)到發(fā)布的目的。
寫(xiě)到這里,發(fā)布者Publisher已經(jīng)完成。那么下面開(kāi)始寫(xiě)訂閱者Subscriber,如上面所說(shuō)其實(shí)訂閱者就是一個(gè) 回調(diào)函數(shù),例如:
let subscriber = function (param) { //do something }
所以下面將整個(gè)代碼展示并演示下效果:
let publisher = { registration: {}, subscribe: function (type, fn) { if (Object.keys(this.registration).indexOf(type) >= 0) { this.registration[type].push(fn); } else { this.registration[type] = []; this.registration[type].push(fn); } }, unSubscribe: function (type, fnName) { if (Object.keys(this.registration).indexOf(type) >= 0) { let index = -1; this.registration[type].forEach(function (func, idx) { if (func.name === fnName) { index = idx; } }) index > -1 ? this.registration[type].splice(index, 1) : null } }, publish: function (type, message) { if (Object.keys(this.registration).indexOf(type) >= 0) { for (let fn of this.registration[type]) { fn(message) } } } } let subscriberA = function (message) { console.log(`A收到通知:${message}`) }; let subscriberB = function (message) { console.log(`B收到通知:${message}`) }; let subscriberC = function (message) { console.log(`C收到通知:${message}`) }; publisher.subscribe("game", subscriberA); publisher.subscribe("game", subscriberB); publisher.subscribe("game", subscriberC); publisher.publish("game", "恭喜RNG獲得LOL 2018季中賽冠軍!")
運(yùn)行看下結(jié)果:
結(jié)果如想象中一樣。
那再試一下取消訂閱,在 publish 之前加一段
publisher.unSubscribe("game", subscriberB.name)
再運(yùn)行看下結(jié)果:
我們已經(jīng)看到 訂閱者B 在取消訂閱后就沒(méi)再收到任何消息。
其實(shí)觀察者模式能做的東西還有很多,比如事件的監(jiān)聽(tīng)、狀態(tài)發(fā)生變化時(shí)的廣播等等。已經(jīng)有過(guò)接觸的朋友都可能意識(shí)到這個(gè)模式特別靈活,在兩個(gè)角色之間正常通信的同時(shí)也盡可能得實(shí)現(xiàn)了解耦,給開(kāi)發(fā)帶來(lái)極大的便利。其中有名的 Knockout 的核心之一就是觀察者模式,所以說(shuō)觀察者模式在前端開(kāi)發(fā)中起到了舉足輕重的作用。
源碼在這,有興趣的朋友可以看下
好了,寫(xiě)到這里本篇博客就結(jié)束了。有問(wèn)題的朋友可以在下方討論;如果文章有不足或者錯(cuò)誤的地方,煩請(qǐng)大家多多指正。Thanks !!!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/95743.html
摘要:閉包與柯里化閉包有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中變量的函數(shù)。柯里化把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)最初函數(shù)的第一個(gè)參數(shù)的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。 本回內(nèi)容介紹 上一回聊到JS的Object類型,簡(jiǎn)單模擬了一下Java的Map,介一講,偶們來(lái)聊一下函數(shù)好唔好,介可系JS世界的一等公民喲。從函數(shù)開(kāi)始,我們就將逐步過(guò)渡到設(shè)計(jì)模式,來(lái)吧,帥狐帶你裝逼帶你飛:...
摘要:介一回,偶們來(lái)聊一下用中的類,有些盆友可能用過(guò)或者的,知道語(yǔ)法糖,可是在中并沒(méi)有,中需要用到構(gòu)造函數(shù)來(lái)模擬類。而且要注意一點(diǎn),構(gòu)造函數(shù)沒(méi)有語(yǔ)句,是自動(dòng)返回。 本回內(nèi)容介紹 上一回聊到JS的Function類型,做了柯里化,數(shù)組去重,排序的題。 介一回,偶們來(lái)聊一下用JS中的類,有些盆友可能用過(guò)ES6或者TypeScript的,知道Class語(yǔ)法糖,可是在ES5中并沒(méi)有,ES5中需要用到...
摘要:插件開(kāi)發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實(shí)現(xiàn)文件分片斷點(diǎn)續(xù)傳。 Vue.js 插件開(kāi)發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。插....
摘要:本回內(nèi)容介紹上一回聊到工廠模式,略抽象。官方說(shuō)法,門(mén)面模式是指提供一個(gè)統(tǒng)一的接口去訪問(wèn)多個(gè)子系統(tǒng)的多個(gè)不同的接口,為子系統(tǒng)中的一組接口提供一個(gè)統(tǒng)一的高層接口。使得子系統(tǒng)更容易使用。 本回內(nèi)容介紹 上一回聊到工廠模式,略抽象。介一回,咱聊門(mén)面模式就比較容易了,門(mén)面模式也叫外觀模式(facade)。官方說(shuō)法,門(mén)面模式是指提供一個(gè)統(tǒng)一的接口去訪問(wèn)多個(gè)子系統(tǒng)的多個(gè)不同的接口,為子系統(tǒng)中的一組接...
閱讀 2833·2021-11-25 09:43
閱讀 2477·2021-10-09 09:44
閱讀 2801·2021-09-22 15:49
閱讀 2568·2021-09-01 11:43
閱讀 2542·2019-08-30 14:16
閱讀 465·2019-08-29 17:24
閱讀 3020·2019-08-29 14:00
閱讀 1384·2019-08-29 13:05