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