自定義事件觀察者及擴展

cathong 7年前發布 | 6K 次閱讀 Android開發 移動開發 JavaScript

事件觀察者的應用

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

事件觀察者

首先,事件觀察者監聽事件,然后當收到事件觸發時,調用事件響應函數,完成一次事件的變遷。

那么,事件觀察者內部會存在一個事件列表來維護事件綁定,即為 eventMap ,其中key是唯一值是事件ID,通過區分事件ID來劃分事件監聽函數。

  1. 一個事件ID對應多個響應事件
  2. 事件ID不可重復
  3. 同一個事件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

 

 本文由用戶 cathong 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!