当前位置: 首页 > 工具软件 > EventProxy > 使用案例 >

eventProxy解析

况经纬
2023-12-01

  在写node.js的时候,经常会遇到要去数据库的多个地方取得多个数据,然后才能进行下一步的操作的情况。如果是线性执行的语言,通常的做法是一条一条去取,全部取到之后再进行下一步操作。然而在node里面,因为是基于事件的,所以只能够一层一层的在回调函数里面嵌套进去。取到第一个数据之后,执行回调函数去取第二条数据,然后再执行回调函数。
   
对于node来说,这样是效率低下的,因为很多数据并不是需要先取完A再去取B的,而是可以同时取AB的,然而通过回调函数嵌套的做法,只能够顺序去取数据(和线性执行的语言一样),同时会导致代码嵌套过多难以阅读。
   
解决这个问题的一个方法是通过事件的方式。遇到要取多个数据的时候,就让它们都去取数据,取到数据之后,抛出一个取到数据的事件,同时开一个监听函数,监听这些事件,当所有的数据都取到了,监听函数接收到了所有数据的时候,开始执行后面的步骤。这样可以尽可能的让数据获取能够同步进行(如果有限制关系:如先取到文章才能取到文章的评论,同样可以用事件的方式解决,不过依旧要按照顺序去获取数据),同时代码也会简洁易懂,没有多层的嵌套。

   
然而想法是美好的,node.js提供的eventemit却并不支持监听多个事件。eventProxy模块就是基于事件的对上述问题的一个解决方案。
   https://github.com/JacksonTian/eventproxy @朴灵

1.应用场景:
   1)最前面举例的时候说到,在写node的时候可以并行处理数据请求,解除多重回调嵌套。
   2)在前端页面渲染的时候,可以通过指定当多个事件触发之后(获取模板/获取数据)再开始渲染。
   ...

2.使用方法

通过assign方法监听多个事件:(只监听一次,触发后就被解除(once))
var EventProxy = require('EventProxy.js').EventProxy;
var event = new EventProxy();
event.assign( " 1 " " 2 " " 3 " " 4 " function (){
    
for  ( var  i = 0 ; i != 4 ++ i){
        console.log(arguments[i]);
    }
});

console.log(
" first " );
event.emit(
" 1 " 1 );
event.emit(
" 2 " 2 );
event.emit(
" 3 " 3 );
event.emit(
" 3 " 3.5 );
event.emit(
" 4 " 4 );
console.log(
" second " );
event.emit(
" 1 " 1 );
event.emit(
" 2 " 2 );
event.emit(
" 3 " 3 );
event.emit(
" 4 " 4 );
/* *
 * 结果输出:
 * first
 * 1
 * 2
 * 3
 * 4
 * second
 *
*/
    如果将上面的代码中的assign方法替换成asignAll方法监听多个事件:(持续监听(bind))

event.assignAll( " 1 " " 2 " " 3 " " 4 " function (){
    
for  ( var  i = 0 ; i != 4 ++ i){
        console.log(arguments[i]);
    }
});

/* *
 * 结果输出:
 *first
 *1
 *2
 *3
 *4
 *second
 *1
 *2
 *3
 *4
 *1
 *2
 *3
 *4
 *1
 *2
 *3
 *4
 *1
 *2
 *3
 *4
 *
*/

3.源码实现

     eventProxy 对于事件的实现方法:

var  EventProxy  =   function  () {
    
this ._callbacks  =  {};
    
this ._fired  =  {};
};

 

   _callbacks用来存放所有的回调函数,对于一个事件en,每一次绑定都将在_callbacks中的_callbacks[en]这个数组中插入这个回调函数。(_callbacks用object来作为一个集合容器,而_callbacks[ev]用数组方式,在unbind的时候,需要遍历数组去找到要解除绑定的函数)

EventProxy.prototype.bind  =  EventProxy.prototype.on  =  EventProxy.prototype.addListener  =   function (ev, callback) {
    
var  list   =   this ._callbacks[ev]  ||  ( this ._callbacks[ev]  =  []);
    list.push(callback);
    
return   this ;
};

 

   同样,也可以将事件和回调函数解除绑定。

EventProxy.prototype.unbind  =  EventProxy.prototype.removeListener  =   function (ev, callback) {      var  calls;      if  ( ! ev) { // 如果没有输入参数,则清除全部回调函数          this ._callbacks  =  {};     }  else   if  (calls  =   this ._callbacks) {          if  ( ! callback) { // 如果输入了event,没输入callback,则删除这个event的所有callbacks             calls[ev]  =  [];         }  else  {          var  list  =  calls[ev];          if  ( ! list)  return   this ;          for  ( var  i  =   0 , l  =  list.length; i  <  l; i ++ ) {              if  (callback  ===  list[i]) {                 list[i]  =   null ;  // 最后,将找到的这个回调函数指为null(为什么先指为null而到后面等到trigger的时候再删除?)                  break ;             }         }         }     }      return   this ; };


这个函数可以看出,js可以很灵活的通过参数传递的不同来实现不同的效果,比之C++的实现方式要容易。


evnetProxy还提供了once函数,注册的回调函数在执行一次之后就会被unbind。

EventProxy.prototype.once  =   function  (ev, callback) {      var  self  =   this ;      this .bind(ev,  function  () {         callback.apply(self, arguments);         self.unbind(ev, arguments.callee); // callee是当前函数的引用     });      return   this ; };


同样和eventemit一样,eventProxy也有事件触发函数emit/fire/trigger,触发eventName事件。

EventProxy.prototype.emit  =  EventProxy.prototype.fire  =  EventProxy.prototype.trigger  =   function (eventName, data, data2) {      var  list, calls, ev, callback, args, i, l;      var  both  =   2 ;     // 其实每次都会触发两个事件      if  ( ! (calls  =   this ._callbacks))  return   this ;      while  (both -- ) {             ev  =  both  ?  eventName : 'all';     // 第一次是触发eventName事件,第二次触发'all'事件,这个事件在后面提及的assign监听多个事件的时候会起到关键作用。          if  (list  =  calls[ev]) {              for  (i  =   0 , l  =  list.length; i  <  l; i ++ ) {                  if  ( ! (callback  =  list[i])) {                     list.splice(i,  1 ); i -- ; l -- ;     // 触发一次这个事件的时候,遍历它,同时将unbind时设置为null的事件从数组删除。                 }  else  {                     args  =  both  ?  Array.prototype.slice.call(arguments,  1 ) : arguments;                     callback.apply( this , args); // 如果是eventName事件,则将data参数传给callback,如果是'all'事件,则把所有参数传递进去。                 }             }         }     }      return   this ; };

 


到此为止,已经可以实现node自带的eventEmit的功能了。On/addListener注册事件,加入callback集合中,然后通过emit/fire/trigger来触发这些事件。但是nevetProxy最重要的作用是下面这个_assign方法,它可以同时监听多个事件,只有这些事件都被触发以后才会执行回调函数,同时每一个事件都可以把参数传递给回调函数。


  
  
var  _assign  =   function  (eventname1, eventname2, cb, once) {      var  proxy  =   this , length, index  =   0 , argsLength  =  arguments.length,         callback, events, isOnce, times  =   0 , flag  =  {};      //  Check the arguments length.      if  (argsLength  <   3 ) {          return   this ;     }     events  =  [].slice.apply(arguments, [ 0 , argsLength  -   2 ]);     callback  =  arguments[argsLength  -   2 ];     isOnce  =  arguments[argsLength  -   1 ];      //  Check the callback type.      if  ( typeof  callback  !==   " function " ) {          return   this ;     }     length  =  events.length;      var  bind  =   function  (key) {              var  method  =  isOnce  ?   " once "  :  " bind " ; // 函数有两种绑定方法,绑定一次或者绑定多次             proxy[method](key,  function  (data) {                     proxy._fired[key]  =  proxy._fired[key]  ||  {};                     proxy._fired[key].data  =  data;                      if  ( ! flag[key]) {                         flag[key]  =   true ;                         Times ++ ;         // 计数器,每有一个事件被触发,计数器+1                     }                 });         };      for  (index  =   0 ; index  <  length; index ++ ) { // 对所有要监听事件调用上面的bind方法,为其绑定一个回调函数,当触发这个事件的时候,把数据保存下来。         bind(events[index]);     }      var  all  =   function  () {     // 当计数器的计数等于事件数目(说明所有事件都被触发了一次),就把所有获取到的数据汇总,然后交给真正的回调函数cb执行。          if  (times  <  length) {              return ;         }          var  data  =  [];          for  (index  =   0 ; index  <  length; index ++ ) {             data.push(proxy._fired[events[index]].data);         }          if  (isOnce) {     // 如果只监听一次,则解除这个'all'的监听。反之则继续监听这个事件。             proxy.unbind( " all " , all);         }         callback.apply( null , data);     };     proxy.bind( " all " , all);     // 绑定'all'事件和上面的all函数,而每当一个事件被fire的时候,都会抛出一个'all'事件,使得all函数被调用,去检查是否所有被监听的事件都被fire过了。 };
上面的函数被下面两个接口函数调用,实现两种不同的功能。 
 //当所有事件都发生之后,执行一次cb,然后撤销监听。 EventProxy.prototype.assign = function (eventname1, eventname2, cb) {
     var  args  =  [].slice.call(arguments);
    args.push(
true );
    _assign.apply(
this , args);
    
return   this ;
};  
// 当所有事件都发生之后,执行一次cb,然后当任何一个事件再发生的时候,就会继续执行cb。
EventProxy.prototype.assignAll  =  EventProxy.prototype.assignAlways  =   function  () {
    
var  args  =  [].slice.call(arguments);
    args.push(
false );
    _assign.apply(
this , args);
    
return   this ;
};
assign运用:当在数据库取数据的时候,可以同时去取N个数据,当得到所有的结果之后,再执行后续的处理。
Assignall运用: 
1.例如一个股票软件,需要定时更新数据。在模板ok之前来的数据也没有用的。只有在数据和模板都ok之后,才第一次渲染, 这之后,数据每次更新都需要重新渲染。
2.对现有结果进行搜索的场景,之前在过滤数据之后,需要更新dom。
 
通过eventProxy,可以充分的发挥js的异步/基于事件的特性,同步进行多个操作。同时也可以让代码更加简洁自然,消除多重嵌套。
 类似资料: