UDN-企业互联网技术人气社区

板块导航

浏览  : 503
回复  : 2

[原生js] 自定义事件观察者及扩展

[复制链接]
王许柔的头像 楼主
发表于 2017-1-6 15:00:10 | 显示全部楼层 |阅读模式
  事件观察者的应用

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

  事件观察者
4.png

  首先,事件观察者监听事件,然后当收到事件触发时,调用事件响应函数,完成一次事件的变迁。

  那么,事件观察者内部会存在一个事件列表来维护事件绑定,即为 eventMap ,其中key是唯一值是事件ID,通过区分事件ID来划分事件监听函数。

  • 一个事件ID对应多个响应事件
  • 事件ID不可重复

  同一个事件ID的事件响应函数不可重复。
3.png

  事件列表中的每一个元素可以包涵4个元素,我使用 TypeScript 实现整个类。

  eventID 事件ID

  callback 事件响应函数

  thisObj 作用域指针

  once 是否触发一次

  定义 EventDispatcher 类
  1. module app {
  2.     export class EventDispatcher {
  3.         private _eventMap: any = {};

  4.         public static create(): app.EventDispatcher {
  5.             var instance = new app.EventDispatcher();
  6.             return instance;
  7.         }
  8.     }
  9. }
复制代码

  定义 on、once 监听函数

  on 函数只要不移除事件,只要事件触发就会响应。

  once 函数只监听一次事件,事件触发一次后就会移除。
  1. public on(eventID: any, callback: Function, thisObj?: any): EventDispatcher {
  2.     this.addEventListener(eventID, callback, thisObj);
  3.     return this;
  4. }

  5. public once(eventID: any, callback: Function, thisObj?: any): EventDispatcher {
  6.     this.addEventListener(eventID, callback, thisObj, true);
  7.     return this;
  8. }

  9. public has(eventID: any, callback: Function, thisObj?: any): boolean {
  10.     return this.hasEventListener(eventID, callback, thisObj);
  11. }

  12. private addEventListener(eventID: any, callback: Function, thisObj: any, once: boolean = false): void {
  13.     if (this.hasEventListener(eventID, callback, thisObj)) return console.log('repeat add Event');
  14.     var list: Array<any> = this._eventMap[eventID];
  15.     if (!list) {
  16.         list = [];
  17.         this._eventMap[eventID] = list;
  18.     }
  19.     list.push({ eventID: eventID, callback: callback, thisObj: thisObj, once: once });
  20. }

  21. private hasEventListener(eventID: any, callback: Function, thisObj: any): boolean {
  22.     var list: Array<any> = this._eventMap[eventID];
  23.     if (!list) return false;
  24.     var len: number = list.length;
  25.     for (var idx = 0; idx < len; idx++) {
  26.         var eventData = list[idx];
  27.         if (eventData &&
  28.             eventData.callback === callback &&
  29.             eventData.thisObj === thisObj) {
  30.             return true;
  31.         }
  32.     }
  33.     return false;
  34. }
复制代码

  定义 off、offAll 移除函数

  off 函数接受 事件ID、响应函数、作用域,根据这三个参数来确定移除哪个响应

  offAll 函数接受 事件ID 时移除指定事件ID的所有响应函数列表,如果不传值,则删除所有的响应函数。
  1. public off(eventID: any, callback: Function, thisObj?: any): EventDispatcher {
  2.     this.removeEventListener(eventID, callback, thisObj);
  3.     return this;
  4. }

  5. public offAll(eventID?: any): EventDispatcher {
  6.     if (eventID) {
  7.         delete this._eventMap[eventID];
  8.     }
  9.     else {
  10.         this._eventMap = {};
  11.     }
  12.     return this;
  13. }

  14. private removeEventListener(eventID: any, callback: Function, thisObj: any): void {
  15.     var list: Array<any> = this._eventMap[eventID];
  16.     var len: number = list.length;
  17.     for (var idx = 0; idx < len; idx++) {
  18.         var eventData = list[idx];
  19.         if (eventData &&
  20.             eventData.callback === callback &&
  21.             eventData.thisObj === thisObj) {
  22.             list.splice(idx, 1);
  23.             break;
  24.         }
  25.     }
  26. }
复制代码

  定义 emit 函数

  emit 函数接受事件ID、传递参数数据。

  通过循环遍历依次响应事件列表,如果是once则响应后直接删除。

  emit(事件, data);
  1. public emit(eventID: any, data?: any): EventDispatcher {
  2.     this.dispatchEvent(eventID, data);
  3.     return this;
  4. }

  5. private dispatchEvent(eventID: any, data?: any): void {
  6.     var list: Array<any> = this._eventMap[eventID];
  7.     if (!list) return;
  8.     var cloneList: Array<any> = list.slice(0);
  9.     var len: number = cloneList.length;
  10.     for (var idx = 0; idx < len; idx++) {
  11.         var eventData = cloneList[idx];
  12.         eventData.once && removeEventListener(eventID, eventData.callback, eventData.thisObj);
  13.         eventData.callback.call(eventData.thisObj, data);
  14.     }
  15. }
复制代码

  扩展定义 all 函数

  all 函数接受一个事件ID列表,目的是当函数列表内的所有函数都触发后,才触发响应函数。

  这里使用了闭包,通过包内作用域,调用 once 函数监听及收集数据并响应函数。

  all([ 事件1,事件2,事件3 ], callback, thisObj);
  1. public all(events: any[], callback: Function, thisObj?: any): EventDispatcher {
  2.     if (!events || events.length == 0 || !callback) throw 'events or callback is null!'
  3.     let proxy = this;
  4.     let datas: any = {};
  5.     let eventsClone = events.concat();
  6.     eventsClone.forEach(function (item) {
  7.         proxy.once(item, function (data) {
  8.             datas[item] = data;
  9.             eventsClone.shift();
  10.             if (eventsClone.length == 0) {
  11.                 setTimeout(function () {
  12.                     callback.call(thisObj, datas);
  13.                 }, 0);
  14.             }
  15.         }, null);
  16.     }, this);
  17.     return this;
  18. }
复制代码

  插件 plugin 模式

  以上定义的函数满足事件观察者的所有特性,也作出了一些写法上的处理如链式调用,使代码更加优雅。

  但是为了功能及便捷,没有永远适合的处理,因此我又加上了 plugin 模式,意思就是 EventDispatcher 可以作为另一个 EventDispatcher 子节点,当父节点的事件触发时,可以向下继续处理子节点的事件响应,直到处理完毕。
2.png

  • 定义pluginMap收集所有的子节点
  • 定义EventDispatcher的唯一索引为Key

  避免子节点与父节点重复,造成死循环。
  1. module app {
  2.     export class EventDispatcher {
  3.         private static EventDispatcher_Hashid: number = 1;
  4.         public hashid: number = EventDispatcher.EventDispatcher_Hashid++;
  5.         private _pluginMap: any = {};
  6.         private _eventMap: any = {};

  7.         public static create(): app.EventDispatcher {
  8.             var instance = new app.EventDispatcher();
  9.             return instance;
  10.         }

  11.         public plugin(ed: EventDispatcher): EventDispatcher {
  12.             if (!ed) throw 'plugin is null'
  13.             if (!this._pluginMap[ed.hashid])
  14.                 this._pluginMap[ed.hashid] = ed;
  15.             return this;
  16.         }

  17.         public unplugin(ed: EventDispatcher): EventDispatcher {
  18.             if (this._pluginMap[ed.hashid]) {
  19.                 delete this._pluginMap[ed.hashid];
  20.             }
  21.             return this;
  22.         }

  23.         public unplugins(): EventDispatcher {
  24.             this._pluginMap = {};
  25.             return this;
  26.         }

  27.         //修改后的 dispatchEvent 函数
  28.         private dispatchEvent(eventID: any, data?: any): void {
  29.             var list: Array<any> = this._eventMap[eventID];
  30.             if (!list) return;
  31.             var cloneList: Array<any> = list.slice(0);
  32.             var len: number = cloneList.length;
  33.             for (var idx = 0; idx < len; idx++) {
  34.                 var eventData = cloneList[idx];
  35.                 eventData.once && removeEventListener(eventID, eventData.callback, eventData.thisObj);
  36.                 eventData.callback.call(eventData.thisObj, data);
  37.             }
  38.             //增加插件的继续处理
  39.             var list = [];
  40.             for (var key in this._pluginMap) {
  41.                 list.push(this._pluginMap[key]);
  42.             }
  43.             list.forEach(function (ed) {
  44.                 ed.emit(eventID, data);
  45.             }, this);
  46.         }
  47.     }
  48. }
复制代码

  只要子节点监听了与父节点相同的事件,父节点触发事件,子节点也会响应。
  1. var ed1 = EventDispatcher.create();
  2. ed1.on('event1',callback1,this);
  3. var ed2 = EventDispatcher.create();
  4. ed2.on('event1',callback2,this);
  5. ed1.plugin(ed2);
  6. ed1.emit('event1');//ed2 同样会触发 event1 事件
  7. ed2.emit('event1');//ed1 不会触发 event1 事件
复制代码

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

相关帖子

发表于 2017-1-6 15:00:40 来自手机 | 显示全部楼层
LZ敢整点更有创意的不?兄弟们等着围观捏~
使用道具 举报

回复

发表于 2017-1-6 15:17:38 | 显示全部楼层
我完全是被标题<<自定义事件观察者及扩展>>吸引过来的
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部