自定義事件觀察者及擴展
事件觀察者的應用
事件觀察者又可以叫事件委托、訂閱模式,目的是為了解偶,定義了一種一對多的關系,當事件變化時通知與此事件依賴的對象,并做出相應的處理。應用時非常廣的,我在做游戲中時必定用到的,是最最基礎的模塊,數據更新、玩家動作觸發、幀頻刷新、服務器消息響應、界面與邏輯分離、狀態變遷等等。我在理解觀察者模式的基礎上作出了一些改動,使用起來會更方便與快捷。
事件觀察者
首先,事件觀察者監聽事件,然后當收到事件觸發時,調用事件響應函數,完成一次事件的變遷。
那么,事件觀察者內部會存在一個事件列表來維護事件綁定,即為 eventMap ,其中key是唯一值是事件ID,通過區分事件ID來劃分事件監聽函數。
- 一個事件ID對應多個響應事件
- 事件ID不可重復
- 同一個事件ID的事件響應函數不可重復。
事件列表中的每一個元素可以包涵4個元素,我使用 TypeScript 實現整個類。
eventID 事件ID
callback 事件響應函數
thisObj 作用域指針
once 是否觸發一次
定義 EventDispatcher 類
module app { export class EventDispatcher { private _eventMap: any = {};public static create(): app.EventDispatcher { var instance = new app.EventDispatcher(); return instance; } }
}</pre>
定義 on、once 監聽函數
on 函數只要不移除事件,只要事件觸發就會響應。
once 函數只監聽一次事件,事件觸發一次后就會移除。
public on(eventID: any, callback: Function, thisObj?: any): EventDispatcher { this.addEventListener(eventID, callback, thisObj); return this; }public once(eventID: any, callback: Function, thisObj?: any): EventDispatcher { this.addEventListener(eventID, callback, thisObj, true); return this; }
public has(eventID: any, callback: Function, thisObj?: any): boolean { return this.hasEventListener(eventID, callback, thisObj); }
private addEventListener(eventID: any, callback: Function, thisObj: any, once: boolean = false): void { if (this.hasEventListener(eventID, callback, thisObj)) return console.log('repeat add Event'); var list: Array<any> = this._eventMap[eventID]; if (!list) { list = []; this._eventMap[eventID] = list; } list.push({ eventID: eventID, callback: callback, thisObj: thisObj, once: once }); }
private hasEventListener(eventID: any, callback: Function, thisObj: any): boolean { var list: Array<any> = this._eventMap[eventID]; if (!list) return false; var len: number = list.length; for (var idx = 0; idx < len; idx++) { var eventData = list[idx]; if (eventData && eventData.callback === callback && eventData.thisObj === thisObj) { return true; } } return false; }</pre>
定義 off、offAll 移除函數
off 函數接受 事件ID、響應函數、作用域,根據這三個參數來確定移除哪個響應
offAll 函數接受 事件ID 時移除指定事件ID的所有響應函數列表,如果不傳值,則刪除所有的響應函數。
public off(eventID: any, callback: Function, thisObj?: any): EventDispatcher { this.removeEventListener(eventID, callback, thisObj); return this; }public offAll(eventID?: any): EventDispatcher { if (eventID) { delete this._eventMap[eventID]; } else { this._eventMap = {}; } return this; }
private removeEventListener(eventID: any, callback: Function, thisObj: any): void { var list: Array<any> = this._eventMap[eventID]; var len: number = list.length; for (var idx = 0; idx < len; idx++) { var eventData = list[idx]; if (eventData && eventData.callback === callback && eventData.thisObj === thisObj) { list.splice(idx, 1); break; } } }</pre>
定義 emit 函數
emit 函數接受事件ID、傳遞參數數據。
通過循環遍歷依次響應事件列表,如果是once則響應后直接刪除。
emit(事件, data);
public emit(eventID: any, data?: any): EventDispatcher { this.dispatchEvent(eventID, data); return this; }private dispatchEvent(eventID: any, data?: any): void { var list: Array<any> = this._eventMap[eventID]; if (!list) return; var cloneList: Array<any> = list.slice(0); var len: number = cloneList.length; for (var idx = 0; idx < len; idx++) { var eventData = cloneList[idx]; eventData.once && removeEventListener(eventID, eventData.callback, eventData.thisObj); eventData.callback.call(eventData.thisObj, data); } }</pre>
擴展定義 all 函數
all 函數接受一個事件ID列表,目的是當函數列表內的所有函數都觸發后,才觸發響應函數。
這里使用了閉包,通過包內作用域,調用 once 函數監聽及收集數據并響應函數。
all([ 事件1,事件2,事件3 ], callback, thisObj);
public all(events: any[], callback: Function, thisObj?: any): EventDispatcher { if (!events || events.length == 0 || !callback) throw 'events or callback is null!' let proxy = this; let datas: any = {}; let eventsClone = events.concat(); eventsClone.forEach(function (item) { proxy.once(item, function (data) { datas[item] = data; eventsClone.shift(); if (eventsClone.length == 0) { setTimeout(function () { callback.call(thisObj, datas); }, 0); } }, null); }, this); return this; }插件 plugin 模式
以上定義的函數滿足事件觀察者的所有特性,也作出了一些寫法上的處理如鏈式調用,使代碼更加優雅。
但是為了功能及便捷,沒有永遠適合的處理,因此我又加上了 plugin 模式,意思就是 EventDispatcher 可以作為另一個 EventDispatcher 子節點,當父節點的事件觸發時,可以向下繼續處理子節點的事件響應,直到處理完畢。
- 定義pluginMap收集所有的子節點
- 定義EventDispatcher的唯一索引為Key
- 避免子節點與父節點重復,造成死循環。
module app { export class EventDispatcher { private static EventDispatcher_Hashid: number = 1; public hashid: number = EventDispatcher.EventDispatcher_Hashid++; private _pluginMap: any = {}; private _eventMap: any = {};public static create(): app.EventDispatcher { var instance = new app.EventDispatcher(); return instance; } public plugin(ed: EventDispatcher): EventDispatcher { if (!ed) throw 'plugin is null' if (!this._pluginMap[ed.hashid]) this._pluginMap[ed.hashid] = ed; return this; } public unplugin(ed: EventDispatcher): EventDispatcher { if (this._pluginMap[ed.hashid]) { delete this._pluginMap[ed.hashid]; } return this; } public unplugins(): EventDispatcher { this._pluginMap = {}; return this; } //修改后的 dispatchEvent 函數 private dispatchEvent(eventID: any, data?: any): void { var list: Array<any> = this._eventMap[eventID]; if (!list) return; var cloneList: Array<any> = list.slice(0); var len: number = cloneList.length; for (var idx = 0; idx < len; idx++) { var eventData = cloneList[idx]; eventData.once && removeEventListener(eventID, eventData.callback, eventData.thisObj); eventData.callback.call(eventData.thisObj, data); } //增加插件的繼續處理 var list = []; for (var key in this._pluginMap) { list.push(this._pluginMap[key]); } list.forEach(function (ed) { ed.emit(eventID, data); }, this); } }
}</pre>
只要子節點監聽了與父節點相同的事件,父節點觸發事件,子節點也會響應。
var ed1 = EventDispatcher.create(); ed1.on('event1',callback1,this); var ed2 = EventDispatcher.create(); ed2.on('event1',callback2,this); ed1.plugin(ed2); ed1.emit('event1');//ed2 同樣會觸發 event1 事件 ed2.emit('event1');//ed1 不會觸發 event1 事件到此為止,事件觀察者的功能已經挺完善的了,但是需求時在變更的,如果有更好更便捷更有趣的功能,會繼續加上!:laughing:
來自:http://www.cnblogs.com/Richard-Core/p/eventdispatcher-plugin.html