自定义事件观察者及扩展
事件观察者的应用
事件观察者又可以叫事件委托、订阅模式,目的是为了解偶,定义了一种一对多的关系,当事件变化时通知与此事件依赖的对象,并做出相应的处理。应用时非常广的,我在做游戏中时必定用到的,是最最基础的模块,数据更新、玩家动作触发、帧频刷新、服务器消息响应、界面与逻辑分离、状态变迁等等。我在理解观察者模式的基础上作出了一些改动,使用起来会更方便与快捷。
事件观察者
首先,事件观察者监听事件,然后当收到事件触发时,调用事件响应函数,完成一次事件的变迁。
那么,事件观察者内部会存在一个事件列表来维护事件绑定,即为 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;
}
}
}
定义 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 事件
到此为止,事件观察者的功能已经挺完善的了,但是需求时在变更的,如果有更好更便捷更有趣的功能,会继续加上!😆