自定义事件观察者及扩展

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>