美文网首页前端进阶之路
EventProxy 源码分析

EventProxy 源码分析

作者: wpzero | 来源:发表于2016-03-22 13:46 被阅读0次

    对朴大的eventproxy的解读。

    上来就把要用的一些通用方法,抽离出来。

     var SLICE = Array.prototype.slice;
     var CONCAT = Array.prototype.concat;
     var later = (typeof setImmediate !== 'undefined' && setImmediate) ||
       (typeof process !== 'undefined' && process.nextTick) || function (fn) {
       setTimeout(fn, 0);
    };
    

    其中later是异步的作用,这里是根据不同的环境来确定later的,
    如果有setImmediate就用setImmediate这个nodejs给我们的异步函数,如果没有就用process.nextTick这个nodejs的loop函数,如果实在没有就用timer这个比较消耗资源的setTimeout。

    一些常量的定义

    var ALL_EVENT = '__all__';
    

    这个ALL_EVENT是个event名称(代表所有的event都会触发的事件名称)

    eventproxy的构造函数

      var EventProxy = function () {
        // 这个考虑到了如果把这个constuctor当做普通的函数调用, 那么也返回 new EventProxy
        if (!(this instanceof EventProxy)) {
          return new EventProxy();
        }
      // 这个用于记录event事件和callback关系的数据结构
        this._callbacks = {};
      // 在all或tail方法中存取相应订阅的data
        this._fired = {};
      };
    

    这里先提前说一下,其实eventproxy主要就是订阅/发布设计模式的实现。
    那么下面的那个方法就是核心的方法之一

    订阅

      EventProxy.prototype.addListener = function (ev, callback) {
        // 就是往_callbacks上边根据事件名称,绑定方法
        this._callbacks[ev] = this._callbacks[ev] || [];
        this._callbacks[ev].push(callback);
        return this;
      };
    

    下面是addListener的一些alias和扩展

      /**
       * `addListener` alias, `bind`
       */
      EventProxy.prototype.bind = EventProxy.prototype.addListener;
      /**
       * `addListener` alias, `on`
       */
      EventProxy.prototype.on = EventProxy.prototype.addListener;
      /**
       * `addListener` alias, `subscribe`
       */
      EventProxy.prototype.subscribe = EventProxy.prototype.addListener;
      // 订阅所有的事件
      EventProxy.prototype.bindForAll = function (callback) {
        this.bind(ALL_EVENT, callback);
      };
    

    和addListener方法相似的另一个方法,和addListener主要区别这个方法是把订阅的函数unshift到相应事件array的头部,而不是push到array的尾部。
    下一个方法是removeListener,移除订阅。

       // 用于移除事件的method
      EventProxy.prototype.removeListener = function (eventname, callback) {
        var calls = this._callbacks;
        // 如果没有事件的名称,就把所有的事件全部clear掉
        if (!eventname) {
          this._callbacks = {};
        } else {
          // 如果没有指定callback的话,把该事件所有的callback都一起删掉
          if (!callback) {
            debug('Remove all listeners of %s', eventname);
            calls[eventname] = [];
          } else {
          // 如果指定了callback,有递归一下把该callback原来的位置 null
            var list = calls[eventname];
            if (list) {
              var l = list.length;
              for (var i = 0; i < l; i++) {
                if (callback === list[i]) {
                  list[i] = null;
                }
              }
            }
          }
        }
        return this;
      };
    
    

    移除订阅的一些alias和扩展

    EventProxy.prototype.unbind = EventProxy.prototype.removeListener;
    
    EventProxy.prototype.removeAllListeners = function (event) {
       return this.unbind(event);
    };
    
    EventProxy.prototype.unbindForAll = function (callback) {
       this.unbind(ALL_EVENT, callback);
    };
    

    发布

      EventProxy.prototype.trigger = function (eventname, data) {
        var list, ev, callback, i, l;
        var both = 2;
        var calls = this._callbacks;
        debug('Emit event %s with data %j', eventname, data);
        while (both--) {
          // 1 为 eventname 0 为 ALL_EVENT
          ev = both ? eventname : ALL_EVENT;
          list = calls[ev];
          if (list) {
            for (i = 0, l = list.length; i < l; i++) {
              // 如果callback = nil 去掉
              // unbind掉的callback
              if (!(callback = list[i])) {
                list.splice(i, 1);
                i--;
                l--;
              } else {
                var args = [];
                var start = both ? 1 : 0;
                for (var j = start; j < arguments.length; j++) {
                  args.push(arguments[j]);
                }
                callback.apply(this, args);
              }
            }
          }
        }
        return this;
      };
    

    这里注意会先触发eventName这个事件的所有订阅函数,然后触发ALL_EVENT的所有订阅函数。
    发布函数的alias们

    EventProxy.prototype.emit = EventProxy.prototype.trigger;
    EventProxy.prototype.fire = EventProxy.prototype.trigger;
    

    其实上面这几个方法已经实现了基础的订阅/发布者模式。
    下面是一些扩展,也是解决callback痛点的主要部分。

    once订阅

      EventProxy.prototype.once = function (ev, callback) {
        var self = this;
        // wrapper一下,添加一个unbind
        //一个对高阶函数的应用
        var wrapper = function () {
          callback.apply(self, arguments);
          self.unbind(ev, wrapper);
        };
        this.bind(ev, wrapper);
        return this;
      };
    

    这个其实就是对高阶函数的一个应用,wrapper一下callback添加一些逻辑,这里是添加了self.unbind(ev, wrapper)这个逻辑。

    emitLater

    异步发布事件,利用了前边介绍的later方法,代码如下:

      EventProxy.prototype.emitLater = function () {
        var self = this;
        var args = arguments;
        later(function () {
          self.trigger.apply(self, args);
        });
      };
    

    immediate 绑定一个事件,然后立刻触发它

      EventProxy.prototype.immediate = function (ev, callback, data) {
        this.bind(ev, callback);
        this.trigger(ev, data);
        return this;
      };
    
      /**
       * `immediate` alias
       */
      EventProxy.prototype.asap = EventProxy.prototype.immediate;
    

    all和tail方法

    all和tail的helper

      // 用于绑定all和tail的helper method
      var _assign = function (eventname1, eventname2, cb, once) {
        var proxy = this;
        // 参数的个数
        var argsLength = arguments.length;
        // 判断被回调的次数
        var times = 0;
        // 标记是否执行过
        var flag = {};
    
        // 参数肯定是大于三的
        // Check the arguments length.
        if (argsLength < 3) {
          return this;
        }
        // 事件 arr
        var events = SLICE.call(arguments, 0, -2);
        // 回调函数
        var callback = arguments[argsLength - 2];
        // 是否是一次
        var isOnce = arguments[argsLength - 1];
    
        // Check the callback type.
        if (typeof callback !== "function") {
          return this;
        }
    
        debug('Assign listener for events %j, once is %s', events, !!isOnce);
    
        // 用于绑定事件的helper
        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++;
            }
          });
        };
    
        // 绑定所有的事件
        var length = events.length;
        for (var index = 0; index < length; index++) {
          bind(events[index]);
        }
    
        // 这个是all_event的callback
        var _all = function (event) {
          // 没有全部执行完(不是所有的事件都执行完毕)
          if (times < length) {
            return;
          }
    
          // 如果这个事件没有执行结束,return
          if (!flag[event]) {
            return;
          }
    
          var data = [];
          for (var index = 0; index < length; index++) {
            data.push(proxy._fired[events[index]].data);
          }
    
          if (isOnce) {
            proxy.unbindForAll(_all);
          }
    
          debug('Events %j all emited with data %j', events, data);
          callback.apply(null, data);
        };
    
        proxy.bindForAll(_all);
      };
    

    all调用这个helper用once为true,tail调用这个helper用once为false。

      EventProxy.prototype.all = function (eventname1, eventname2, callback) {
        var args = CONCAT.apply([], arguments);
        // push true 来确定是once
        args.push(true);
        _assign.apply(this, args);
        return this;
      };
    
    
      /**
       * `all` alias
       */
      EventProxy.prototype.assign = EventProxy.prototype.all;
    
    
      EventProxy.prototype.tail = function () {
        var args = CONCAT.apply([], arguments);
        // 不是一次的
        args.push(false);
        _assign.apply(this, args);
        return this;
      };
    
      /**
       * `tail` alias, assignAll
       */
      EventProxy.prototype.assignAll = EventProxy.prototype.tail;
      /**
       * `tail` alias, assignAlways
       */
      EventProxy.prototype.assignAlways = EventProxy.prototype.tail;
    

    after方法

      EventProxy.prototype.after = function (eventname, times, callback) {
        if (times === 0) {
          callback.call(null, []);
          return this;
        }
        var proxy = this,
          // 这个用于存无序的after调用的结果
          firedData = [];
        this._after = this._after || {};
    
        var group = eventname + '_group';
    
        // 这个用于存取有序的group的结果
        this._after[group] = {
          index: 0,
          results: []
        };
    
        debug('After emit %s times, event %s\'s listenner will execute', times, eventname);
        var all = function (name, data) {
          if (name === eventname) {
            times--;
            firedData.push(data);
            if (times < 1) {
              debug('Event %s was emit %s, and execute the listenner', eventname, times);
              proxy.unbindForAll(all);
              callback.apply(null, [firedData]);
            }
          }
          // 如果是group来emit的,会保持emit的顺序,把数据放到_after[group].results中
          if (name === group) {
            times--;
            proxy._after[group].results[data.index] = data.result;
            if (times < 1) {
              debug('Event %s was emit %s, and execute the listenner', eventname, times);
              proxy.unbindForAll(all);
              callback.call(null, proxy._after[group].results);
            }
          }
        };
        proxy.bindForAll(all);
        return this;
      };
    
      EventProxy.prototype.group = function (eventname, callback) {
        var that = this;
        var group = eventname + '_group';
        var index = that._after[group].index;
        // 这个用于记录第几个emit
        that._after[group].index++;
        return function (err, data) {
          if (err) {
            // put all arguments to the error handler
            return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
          }
          that.emit(group, {
            index: index,
            // callback(err, args1, args2, ...)
            result: callback ? callback.apply(null, SLICE.call(arguments, 1)) : data
          });
        };
      };
    

    其中group方法是个特殊emit方法,保证了after订阅函数得到的数据的数组是有序的。

    any方法

      EventProxy.prototype.any = function () {
        var proxy = this,
          callback = arguments[arguments.length - 1],
          events = SLICE.call(arguments, 0, -1),
          _eventname = events.join("_");
    
        debug('Add listenner for Any of events %j emit', events);
        proxy.once(_eventname, callback);
    
        var _bind = function (key) {
          proxy.bind(key, function (data) {
            debug('One of events %j emited, execute the listenner');
            proxy.trigger(_eventname, {"data": data, eventName: key});
          });
        };
    
        for (var index = 0; index < events.length; index++) {
          _bind(events[index]);
        }
      };
    

    错误处理 fail/throw

      EventProxy.prototype.fail = function (callback) {
        var that = this;
    
        that.once('error', function () {
          that.unbind();
          // put all arguments to the error handler
          // fail(function(err, args1, args2, ...){})
          callback.apply(null, arguments);
        });
        return this;
      };
    
      /**
       * A shortcut of ep#emit('error', err)
       */
      EventProxy.prototype.throw = function () {
        var that = this;
        that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
      };
    
    

    done方法

    对错误处理的一层封装

      EventProxy.prototype.done = function (handler, callback) {
        var that = this;
        return function (err, data) {
          if (err) {
            // put all arguments to the error handler
            return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
          }
    
          // callback(err, args1, args2, ...)
          var args = SLICE.call(arguments, 1);
    
          if (typeof handler === 'string') {
            // getAsync(query, ep.done('query'));
            // or
            // getAsync(query, ep.done('query', function (data) {
            //   return data.trim();
            // }));
            if (callback) {
              // only replace the args when it really return a result
              return that.emit(handler, callback.apply(null, args));
            } else {
              // put all arguments to the done handler
              //ep.done('some');
              //ep.on('some', function(args1, args2, ...){});
              return that.emit.apply(that, [handler].concat(args));
            }
          }
    
          // speed improve for mostly case: `callback(err, data)`
          if (arguments.length <= 2) {
            return handler(data);
          }
    
          // callback(err, args1, args2, ...)
          handler.apply(null, args);
        };
      };
    

    总之,学习一下朴大的代码风格,和事件订阅和发布的实现。

    相关文章

      网友评论

        本文标题:EventProxy 源码分析

        本文链接:https://www.haomeiwen.com/subject/opxllttx.html