摘要:觀察者模式觀察者模式廣泛的應(yīng)用于語(yǔ)言中,瀏覽器事件如鼠標(biāo)單擊,鍵盤事件都是該模式的例子??梢钥吹?,這就是觀察者模式的訂閱方法實(shí)現(xiàn)。小結(jié)通過(guò)創(chuàng)建可觀察的對(duì)象,當(dāng)發(fā)生一個(gè)感興趣的事件時(shí)可將該事件通告給所有觀察者,從而形成松散的耦合。
觀察者模式
觀察者模式(observer)廣泛的應(yīng)用于javascript語(yǔ)言中,瀏覽器事件(如鼠標(biāo)單擊click,鍵盤事件keyDown)都是該模式的例子。設(shè)計(jì)這種模式背后的主要原因是促進(jìn)形成低耦合,在這種模式中不是簡(jiǎn)單的對(duì)象調(diào)用對(duì)象,而是一個(gè)對(duì)象“訂閱”另一個(gè)對(duì)象的某個(gè)活動(dòng),當(dāng)對(duì)象的活動(dòng)狀態(tài)發(fā)生了改變,就去通知訂閱者,而訂閱者也稱為觀察者。
報(bào)紙訂閱生活中就像是去報(bào)社訂報(bào)紙,你喜歡讀什么報(bào)就去報(bào)社去交錢訂閱,當(dāng)發(fā)布了新報(bào)紙的時(shí)候,報(bào)社會(huì)向所有訂閱了報(bào)紙的每一個(gè)人發(fā)送一份,訂閱者就可以接收到。
我們可以利用這個(gè)例子來(lái)使用javascript來(lái)模擬一下。假設(shè)有一個(gè)發(fā)布者Jack,它每天出版報(bào)紙雜志,訂閱者Tom將被通知任何時(shí)候發(fā)生的新聞。
Jack要有一個(gè)subscribers屬性,它是一個(gè)數(shù)組類型,訂閱的行為將會(huì)按順序存放在這個(gè)數(shù)組中,而通知意味著調(diào)用訂閱者對(duì)象的某個(gè)方法。因此,當(dāng)用戶Tom訂閱信息的時(shí)候,該訂閱者要向Jack的subscribe()提供他的一個(gè)方法。當(dāng)然也可以退訂,我不想再看報(bào)紙了,就調(diào)用unsubscribe()取消訂閱。
一個(gè)簡(jiǎn)單的觀察者模式應(yīng)有以下成員:
subscribes 一個(gè)數(shù)組
subscribe() 將訂閱添加到數(shù)組里
unsubscribe() 把訂閱從數(shù)組中移除
publish() 迭代數(shù)組,調(diào)用訂閱時(shí)的方法
這個(gè)模式中還需要一個(gè)type參數(shù),用于區(qū)分訂閱的類型,如有的人訂閱的是娛樂(lè)新聞,有的人訂閱的是體育雜志,使用此屬性來(lái)標(biāo)記。
我們使用簡(jiǎn)單的代碼來(lái)實(shí)現(xiàn)它:
var Jack = { subscribers: { "any": [] }, //添加訂閱 subscribe: function (type = "any", fn) { if (!this.subscribers[type]) { this.subscribers[type] = []; } this.subscribers[type].push(fn); //將訂閱方法保存在數(shù)組里 }, //退訂 unsubscribe: function (type = "any", fn) { this.subscribers[type] = this.subscribers[type].filter(function (item) { return item !== fn; }); //將退訂的方法從數(shù)組中移除 }, //發(fā)布訂閱 publish: function (type = "any", ...args) { this.subscribers[type].forEach(function (item) { item(...args); //根據(jù)不同的類型調(diào)用相應(yīng)的方法 }); } };
以上就是一個(gè)最簡(jiǎn)單的觀察者模式的實(shí)現(xiàn),可以看到代碼非常的簡(jiǎn)單,核心原理就是將訂閱的方法按分類存在一個(gè)數(shù)組中,當(dāng)發(fā)布時(shí)取出執(zhí)行即可。
下面使用Tom來(lái)訂報(bào):
var Tom = { readNews: function (info) { console.log(info); } }; //Tom訂閱Jack的報(bào)紙 Jack.subscribe("娛樂(lè)", Tom.readNews); Jack.subscribe("體育", Tom.readNews); //Tom 退訂娛樂(lè)新聞: Jack.unsubscribe("娛樂(lè)", Tom.readNews); //發(fā)布新報(bào)紙: Jack.publish("娛樂(lè)", "S.H.E演唱會(huì)驚喜登臺(tái)") Jack.publish("體育", "歐國(guó)聯(lián)-意大利0-1客負(fù)葡萄牙");
運(yùn)行結(jié)果:
歐國(guó)聯(lián)-意大利0-1客負(fù)葡萄牙觀察者模式的實(shí)際應(yīng)用
可以看到觀察者模式將兩個(gè)對(duì)象的關(guān)系變得十分松散,當(dāng)不需要訂閱關(guān)系的時(shí)候刪掉訂閱的語(yǔ)句即可。那么在實(shí)際應(yīng)用中有哪些地方使用了這個(gè)模式呢?
events模塊node.js的events是一個(gè)使用率很高的模塊,其它原生node.js模塊都是基于它來(lái)完成的,比如流、HTTP等,我們可以手寫一版events的核心代碼,看看觀察者模式的實(shí)際應(yīng)用。
events模塊的功能就是一個(gè)事件綁定,所有繼承自它的實(shí)例都具備事件處理的能力。首先它是一個(gè)類,我們寫出它的基本結(jié)構(gòu):
function EventEmitter() { //私有屬性,保存訂閱方法 this._events = {}; } //默認(rèn)最大監(jiān)聽(tīng)數(shù) EventEmitter.defaultMaxListeners = 10; module.exports = EventEmitter;
下面我們一個(gè)個(gè)將events的核心方法實(shí)現(xiàn)。
on方法首先是on方法,該方法用于訂閱事件,在舊版本的node.js中是addListener方法,它們是同一個(gè)函數(shù):
EventEmitter.prototype.on = EventEmitter.prototype.addListener = function (type, listener, flag) { //保證存在實(shí)例屬性 if (!this._events) this._events = Object.create(null); if (this._events[type]) { if (flag) {//從頭部插入 this._events[type].unshift(listener); } else { this._events[type].push(listener); } } else { this._events[type] = [listener]; } //綁定事件,觸發(fā)newListener if (type !== "newListener") { this.emit("newListener", type); } };
因?yàn)橛衅渌宇愋枰^承自EventEmitter,因此要判斷子類是否存在_event屬性,這樣做是為了保證子類必須存在此實(shí)例屬性。而flag標(biāo)記是一個(gè)訂閱方法的插入標(biāo)識(shí),如果為"true"就視為插入在數(shù)組的頭部。可以看到,這就是觀察者模式的訂閱方法實(shí)現(xiàn)。
emit方法EventEmitter.prototype.emit = function (type, ...args) { if (this._events[type]) { this._events[type].forEach(fn => fn.call(this, ...args)); } };
emit方法就是將訂閱方法取出執(zhí)行,使用call方法來(lái)修正this的指向,使其指向子類的實(shí)例。
once方法EventEmitter.prototype.once = function (type, listener) { let _this = this; //中間函數(shù),在調(diào)用完之后立即刪除訂閱 function only() { listener(); _this.removeListener(type, only); } //origin保存原回調(diào)的引用,用于remove時(shí)的判斷 only.origin = listener; this.on(type, only); };
once方法非常有趣,它的功能是將事件訂閱“一次”,當(dāng)這個(gè)事件觸發(fā)過(guò)就不會(huì)再次觸發(fā)了。其原理是將訂閱的方法再包裹一層函數(shù),在執(zhí)行后將此函數(shù)移除即可。
off方法EventEmitter.prototype.off = EventEmitter.prototype.removeListener = function (type, listener) { if (this._events[type]) { //過(guò)濾掉退訂的方法,從數(shù)組中移除 this._events[type] = this._events[type].filter(fn => { return fn !== listener && fn.origin !== listener }); } };
off方法即為退訂,原理同觀察者模式一樣,將訂閱方法從數(shù)組中移除即可。
prependListener方法EventEmitter.prototype.prependListener = function (type, listener) { this.on(type, listener, true); };
此方法不必多說(shuō)了,調(diào)用on方法將標(biāo)記傳為true(插入訂閱方法在頭部)即可。
以上,就將EventEmitter類的核心方法實(shí)現(xiàn)了。
小結(jié)通過(guò)創(chuàng)建“可觀察的”對(duì)象,當(dāng)發(fā)生一個(gè)感興趣的事件時(shí)可將該事件通告給所有觀察者,從而形成松散的耦合。
部分實(shí)例參考《JavaScript模式》作者:Stoyan Stefanov
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/97694.html
摘要:為指定事件注冊(cè)一個(gè)單次監(jiān)聽(tīng)器,即監(jiān)聽(tīng)器最多只會(huì)觸發(fā)一次,觸發(fā)后立刻解除該監(jiān)聽(tīng)器。移除指定事件的某個(gè)監(jiān)聽(tīng)器,監(jiān)聽(tīng)器必須是該事件已經(jīng)注冊(cè)過(guò)的監(jiān)聽(tīng)器。返回指定事件的監(jiān)聽(tīng)器數(shù)組。如何創(chuàng)建空對(duì)象我們已經(jīng)了解到,是要來(lái)儲(chǔ)存監(jiān)聽(tīng)事件監(jiān)聽(tīng)器數(shù)組的。 毫無(wú)疑問(wèn),nodeJS改變了整個(gè)前端開(kāi)發(fā)生態(tài)。本文通過(guò)分析nodeJS當(dāng)中events模塊源碼,由淺入深,動(dòng)手實(shí)現(xiàn)了屬于自己的ES6事件觀察者系統(tǒng)。千萬(wàn)不...
摘要:發(fā)布訂閱模式訂閱者把自己想訂閱的事件注冊(cè)到調(diào)度中心,當(dāng)發(fā)布者發(fā)布該事件到調(diào)度中心,也就是該事件觸發(fā)時(shí),由調(diào)度中心統(tǒng)一調(diào)度訂閱者注冊(cè)到調(diào)度中心的處理代碼。 發(fā)布-訂閱模式,看似陌生,其實(shí)不然。工作中經(jīng)常會(huì)用到,例如 Node.js EventEmitter 中的 on 和 emit 方法;Vue 中的 $on 和 $emit 方法。他們都使用了發(fā)布-訂閱模式,讓開(kāi)發(fā)變得更加高效方便。 一...
摘要:為什么把叫做集合而不能稱為嚴(yán)格意義上的對(duì)象,來(lái)看這個(gè)集合的構(gòu)造函數(shù)可以見(jiàn)得,是與處于同一層級(jí)的而非是繼承自,所以說(shuō)由實(shí)例出來(lái)的對(duì)象更加的純凈,并沒(méi)有諸如等方法,更像是一個(gè)集合。 寫在前面 事件的編程方式具有輕量級(jí)、松耦合、只關(guān)注事務(wù)點(diǎn)等優(yōu)勢(shì),在瀏覽器端,有著自己的一套DOM事件機(jī)制,其中含包括這諸如事件冒泡,事件捕獲等;然而Node的事件機(jī)制沒(méi)有事件冒泡等,其原理就是設(shè)計(jì)模式中的觀察者...
摘要:本文從的的使用出發(fā),循序漸進(jìn)的實(shí)現(xiàn)一個(gè)完整的模塊。移除指定事件的某個(gè)監(jiān)聽(tīng)器,監(jiān)聽(tīng)器必須是該事件已經(jīng)注冊(cè)過(guò)的監(jiān)聽(tīng)器的別名移除所有事件的所有監(jiān)聽(tīng)器,如果指定事件,則移除指定事件的所有監(jiān)聽(tīng)器。返回指定事件的監(jiān)聽(tīng)器數(shù)組。 node的事件模塊只包含了一個(gè)類:EventEmitter。這個(gè)類在node的內(nèi)置模塊和第三方模塊中大量使用。EventEmitter本質(zhì)上是一個(gè)觀察者模式的實(shí)現(xiàn),這種模式可...
摘要:典型和改造挑戰(zhàn)了解事件發(fā)布訂閱系統(tǒng)實(shí)現(xiàn)思想,我們來(lái)看一段簡(jiǎn)單且典型的基礎(chǔ)實(shí)現(xiàn)上面代碼,實(shí)現(xiàn)了一個(gè)類我們維護(hù)一個(gè)類型的,對(duì)不同事件的所有回調(diào)函數(shù)進(jìn)行維護(hù)。方法對(duì)指定事件進(jìn)行回調(diào)函數(shù)存儲(chǔ)方法對(duì)指定的觸發(fā)事件,逐個(gè)執(zhí)行其回調(diào)函數(shù)。 showImg(https://segmentfault.com/img/remote/1460000014287200); 新書終于截稿,今天稍有空閑,為大家奉...
閱讀 2912·2021-11-22 15:11
閱讀 3634·2021-09-28 09:43
閱讀 2958·2019-08-30 13:05
閱讀 3494·2019-08-30 11:18
閱讀 1508·2019-08-29 16:34
閱讀 1421·2019-08-29 13:53
閱讀 2992·2019-08-29 11:03
閱讀 1728·2019-08-29 10:57