自定义事件观察者及扩展

事件观察者的应用

事件观察者又可以叫事件委托、订阅模式,目的是为了解偶,定义了一种一对多的关系,当事件变化时通知与此事件依赖的对象,并做出相应的处理。应用时非常广的,我在做游戏中时必定用到的,是最最基础的模块,数据更新、玩家动作触发、帧频刷新、服务器消息响应、界面与逻辑分离、状态变迁等等。我在理解观察者模式的基础上作出了一些改动,使用起来会更方便与快捷。

事件观察者

首先,事件观察者监听事件,然后当收到事件触发时,调用事件响应函数,完成一次事件的变迁。
那么,事件观察者内部会存在一个事件列表来维护事件绑定,即为 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;
        }
    }
}

定义 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;
}

定义 off、offAll、offTarget 移除函数

off 函数接受 事件ID、响应函数、作用域,根据这三个参数来确定移除哪个响应
offAll 函数接受 事件ID 时移除指定事件ID的所有响应函数列表,如果不传值,则删除所有的响应函数。
offTarget 函数接受对象指针,通过对象删除所有监听

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;
}

public offTarget(target: any): EventDispatcher {
    this.removeEventListenerByTarget(target);
    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;
        }
    }
}

private removeEventListenerByTarget(thisObj: any): void {
    for (var key in this._eventMap) {
        var list: Array<any> = this._eventMap[key];
        this._eventMap[key] = list.filter(function (value): boolean {
            return !(value.thisObj === thisObj);
        }, this);
    }
}

定义 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 && this.removeEventListener(eventID, eventData.callback, eventData.thisObj);
        eventData.callback.call(eventData.thisObj, data);
    }
}

扩展定义 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 && this.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);
		}
    }
}

只要子节点监听了与父节点相同的事件,父节点触发事件,子节点也会响应。

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 事件

到此为止,事件观察者的功能已经挺完善的了,但是需求时在变更的,如果有更好更便捷更有趣的功能,会继续加上!😆

posted @ 2017-01-02 18:03  richliu1023  阅读(1335)  评论(0编辑  收藏  举报