自定義事件觀察者及擴展
事件觀察者的應用
事件觀察者又可以叫事件委托、訂閱模式,目的是為了解偶,定義了一種一對多的關系,當事件變化時通知與此事件依賴的對象,并做出相應的處理。應用時非常廣的,我在做游戲中時必定用到的,是最最基礎的模塊,數據更新、玩家動作觸發、幀頻刷新、服務器消息響應、界面與邏輯分離、狀態變遷等等。我在理解觀察者模式的基礎上作出了一些改動,使用起來會更方便與快捷。
事件觀察者

首先,事件觀察者監聽事件,然后當收到事件觸發時,調用事件響應函數,完成一次事件的變遷。
那么,事件觀察者內部會存在一個事件列表來維護事件綁定,即為 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