ting-emitter源码阅读

12356

源码分析

// 把订阅名称和发布的事件数组对应好关系
on: function (name, callback, ctx) {
    // 有就使用e,没有就创建
    var e = this.e || (this.e = {});
    // 和e一样的处理,创建事件名称数组(可能一个名称触发多个方法)
    (e[name] || (e[name] = [])).push({
      fn: callback,
      ctx: ctx
    });
    // 可以看到目前全部的订阅关系
    return this;
  }
// 发布
emit: function (name) {
    // data为函数传参
    var data = [].slice.call(arguments, 1);
    // 兼容写法,拿到对应名称的事件数组
    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
    var i = 0;
    var len = evtArr.length;

    for (i; i < len; i++) {
      // 按照进入顺序触发,参数传递给每一个事件
      evtArr[i].fn.apply(evtArr[i].ctx, data);
    }

    return this;
  },
// 取消订阅某个事件
off: function (name, callback) {
    var e = this.e || (this.e = {});
    var evts = e[name];
    var liveEvents = [];

    if (evts && callback) {
      for (var i = 0, len = evts.length; i < len; i++) {
        if (evts[i].fn !== callback && evts[i].fn._ !== callback)
          liveEvents.push(evts[i]);
      }
    }
    // 这里原本是使用splice的,换成新数组的原因是因为splice会改变他原本的数组,for循环中会出现问题。

    // 取消订阅某个事件之后的事件数组,有长度就替换原来的,没有就直接删除这个属性
    // 这里直接删除这个属性可能是为了节省内存,不过delete方法也消耗挺大的
    (liveEvents.length)
      ? e[name] = liveEvents
      : delete e[name];

    return this;
  }
// 订阅一次的事件
once: function (name, callback, ctx) {
    var self = this;
    // 把事件包装一层,闭包
    function listener () {
      self.off(name, listener);
      callback.apply(ctx, arguments);
    };
    // 因为原来的事件被包装了一层,所以使用_属性来存一份副本
    listener._ = callback
    return this.on(name, listener, ctx);
  },

应用场景

  1. 在全局不同地方想要通信时,不能总是在全局定义变量,编程需要遵守一定规范,类似vue中EventBus。

个人理解

  1. 事件订阅发布也是在面试中常见的一个题目,整体就是使用了一个对象来存储对应的名称与事件关系,触发之后对应事件执行。
  2. 但是在大一些的项目中,使用过多也会出现不好找的问题,名称必须唯一,在订阅时的事件名称最好也是唯一,不然使用搜索时,会不好找。
  3. 项目使用都是兼容写法,不需要使用babel来兼容,代码量也很少,应用与一些小项目,小模块,是很不错的选择。