自定义事件观察者及扩展
cathong
8年前
<h2>事件观察者的应用</h2> <p>事件观察者又可以叫事件委托、订阅模式,目的是为了解偶,定义了一种一对多的关系,当事件变化时通知与此事件依赖的对象,并做出相应的处理。应用时非常广的,我在做游戏中时必定用到的,是最最基础的模块,数据更新、玩家动作触发、帧频刷新、服务器消息响应、界面与逻辑分离、状态变迁等等。我在理解观察者模式的基础上作出了一些改动,使用起来会更方便与快捷。</p> <h2>事件观察者</h2> <p style="text-align: center;"><img src="https://simg.open-open.com/show/728f68d2af25569af3774c7e3c2a3c87.png"></p> <p>首先,事件观察者监听事件,然后当收到事件触发时,调用事件响应函数,完成一次事件的变迁。</p> <p>那么,事件观察者内部会存在一个事件列表来维护事件绑定,即为 eventMap ,其中key是唯一值是事件ID,通过区分事件ID来划分事件监听函数。</p> <ol> <li>一个事件ID对应多个响应事件</li> <li>事件ID不可重复</li> <li>同一个事件ID的事件响应函数不可重复。</li> </ol> <p style="text-align: center;"><img src="https://simg.open-open.com/show/26b3aa24b47b8a1970bed8d07fb40d8f.png"></p> <p>事件列表中的每一个元素可以包涵4个元素,我使用 TypeScript 实现整个类。</p> <p>eventID 事件ID</p> <p>callback 事件响应函数</p> <p>thisObj 作用域指针</p> <p>once 是否触发一次</p> <h3>定义 EventDispatcher 类</h3> <pre> module app { export class EventDispatcher { private _eventMap: any = {}; public static create(): app.EventDispatcher { var instance = new app.EventDispatcher(); return instance; } } }</pre> <h3>定义 on、once 监听函数</h3> <p>on 函数只要不移除事件,只要事件触发就会响应。</p> <p>once 函数只监听一次事件,事件触发一次后就会移除。</p> <pre> 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> <h3>定义 off、offAll 移除函数</h3> <p>off 函数接受 事件ID、响应函数、作用域,根据这三个参数来确定移除哪个响应</p> <p>offAll 函数接受 事件ID 时移除指定事件ID的所有响应函数列表,如果不传值,则删除所有的响应函数。</p> <pre> 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> <h3>定义 emit 函数</h3> <p>emit 函数接受事件ID、传递参数数据。</p> <p>通过循环遍历依次响应事件列表,如果是once则响应后直接删除。</p> <p>emit(事件, data);</p> <pre> 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> <h3>扩展定义 all 函数</h3> <p>all 函数接受一个事件ID列表,目的是当函数列表内的所有函数都触发后,才触发响应函数。</p> <p>这里使用了闭包,通过包内作用域,调用 once 函数监听及收集数据并响应函数。</p> <p>all([ 事件1,事件2,事件3 ], callback, thisObj);</p> <pre> 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; }</pre> <h2>插件 plugin 模式</h2> <p>以上定义的函数满足事件观察者的所有特性,也作出了一些写法上的处理如链式调用,使代码更加优雅。</p> <p>但是为了功能及便捷,没有永远适合的处理,因此我又加上了 plugin 模式,意思就是 EventDispatcher 可以作为另一个 EventDispatcher 子节点,当父节点的事件触发时,可以向下继续处理子节点的事件响应,直到处理完毕。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/6a143acf865d568aead6f85bef184974.png"></p> <ul> <li>定义pluginMap收集所有的子节点</li> <li>定义EventDispatcher的唯一索引为Key</li> <li>避免子节点与父节点重复,造成死循环。</li> </ul> <pre> 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> <p>只要子节点监听了与父节点相同的事件,父节点触发事件,子节点也会响应。</p> <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 事件</pre> <p>到此为止,事件观察者的功能已经挺完善的了,但是需求时在变更的,如果有更好更便捷更有趣的功能,会继续加上!:laughing:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/fd7d0ae91b73e86cadcb32996bb36fca.png"></p> <p> </p> <p>来自:http://www.cnblogs.com/Richard-Core/p/eventdispatcher-plugin.html</p> <p> </p>