EventProxy 源碼分析

對樸大的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);
};

其中l(wèi)ater是異步的作用,這里是根據(jù)不同的環(huán)境來確定later的,
如果有setImmediate就用setImmediate這個nodejs給我們的異步函數(shù),如果沒有就用process.nextTick這個nodejs的loop函數(shù),如果實在沒有就用timer這個比較消耗資源的setTimeout。

一些常量的定義

var ALL_EVENT = '__all__';

這個ALL_EVENT是個event名稱(代表所有的event都會觸發(fā)的事件名稱)

eventproxy的構(gòu)造函數(shù)

  var EventProxy = function () {
    // 這個考慮到了如果把這個constuctor當(dāng)做普通的函數(shù)調(diào)用, 那么也返回 new EventProxy
    if (!(this instanceof EventProxy)) {
      return new EventProxy();
    }
  // 這個用于記錄event事件和callback關(guān)系的數(shù)據(jù)結(jié)構(gòu)
    this._callbacks = {};
  // 在all或tail方法中存取相應(yīng)訂閱的data
    this._fired = {};
  };

這里先提前說一下,其實eventproxy主要就是訂閱/發(fā)布設(shè)計模式的實現(xiàn)。
那么下面的那個方法就是核心的方法之一

訂閱

  EventProxy.prototype.addListener = function (ev, callback) {
    // 就是往_callbacks上邊根據(jù)事件名稱,綁定方法
    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主要區(qū)別這個方法是把訂閱的函數(shù)unshift到相應(yīng)事件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);
};

發(fā)布

  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;
  };

這里注意會先觸發(fā)eventName這個事件的所有訂閱函數(shù),然后觸發(fā)ALL_EVENT的所有訂閱函數(shù)。
發(fā)布函數(shù)的alias們

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

其實上面這幾個方法已經(jīng)實現(xiàn)了基礎(chǔ)的訂閱/發(fā)布者模式。
下面是一些擴展,也是解決callback痛點的主要部分。

once訂閱

  EventProxy.prototype.once = function (ev, callback) {
    var self = this;
    // wrapper一下,添加一個unbind
    //一個對高階函數(shù)的應(yīng)用
    var wrapper = function () {
      callback.apply(self, arguments);
      self.unbind(ev, wrapper);
    };
    this.bind(ev, wrapper);
    return this;
  };

這個其實就是對高階函數(shù)的一個應(yīng)用,wrapper一下callback添加一些邏輯,這里是添加了self.unbind(ev, wrapper)這個邏輯。

emitLater

異步發(fā)布事件,利用了前邊介紹的later方法,代碼如下:

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

immediate 綁定一個事件,然后立刻觸發(fā)它

  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;
    // 參數(shù)的個數(shù)
    var argsLength = arguments.length;
    // 判斷被回調(diào)的次數(shù)
    var times = 0;
    // 標(biāo)記是否執(zhí)行過
    var flag = {};

    // 參數(shù)肯定是大于三的
    // Check the arguments length.
    if (argsLength < 3) {
      return this;
    }
    // 事件 arr
    var events = SLICE.call(arguments, 0, -2);
    // 回調(diào)函數(shù)
    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) {
      // 沒有全部執(zhí)行完(不是所有的事件都執(zhí)行完畢)
      if (times < length) {
        return;
      }

      // 如果這個事件沒有執(zhí)行結(jié)束,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調(diào)用這個helper用once為true,tail調(diào)用這個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調(diào)用的結(jié)果
      firedData = [];
    this._after = this._after || {};

    var group = eventname + '_group';

    // 這個用于存取有序的group的結(jié)果
    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的順序,把數(shù)據(jù)放到_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
      });
    };
  };

其中g(shù)roup方法是個特殊emit方法,保證了after訂閱函數(shù)得到的數(shù)據(jù)的數(shù)組是有序的。

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);
    };
  };

總之,學(xué)習(xí)一下樸大的代碼風(fēng)格,和事件訂閱和發(fā)布的實現(xiàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容