# 自定义事件那些事儿

# DOM 元素的自定义事件

对于浏览器中的 DOM 元素来说,事件的实现非常简单,我们只需要调用浏览器 API 提供的 Event 接口就可以了。

语法

event = new Event(typeArg, eventInit);

示例

// 创建一个支持冒泡且不能被取消的look事件

var ev = new Event("look", { bubbles: true, cancelable: false });
document.dispatchEvent(ev);

// 事件可以在任何元素触发,不仅仅是document
myDiv.dispatchEvent(ev);

# 实现一个自定义事件的类

function MyCustomEvent() {
  this._listener = {};
  this.addEvent = (type, fn) => {
    console.log("addEvent", type);
    if (!this._listener[type]) {
      this._listener[type] = [];
    }
    this._listener[type].push(fn);
  };
  this.attachEvent = (type, data) => {
    console.log("attachEvent", type);
    if (this._listener[type] && this._listener[type] instanceof Array) {
      if (this._listener[type].length) {
        this._listener[type].forEach((callback) => {
          callback(data);
        });
      } else {
        console.log(type, "event is not register");
      }
    }
  };
  this.removeEvent = (type, point) => {
    console.log("removeEvent", type);
    if (this._listener[type] && this._listener[type] instanceof Array) {
      let index = this._listener[type].findIndex((callback) => {
        return point === callback;
      });
      if (index > -1) {
        this._listener[type].splice(index, 1);
        point = null;
      }
    }
  };
}
let eventTarget = new MyCustomEvent({});

function hello(e) {
  console.log(e);
}

eventTarget.addEvent("hello", hello);
eventTarget.attachEvent("hello", "Helo This is CustomEvent");
// addEvent hello
// attachEvent hello
// Helo This is CustomEvent

eventTarget.removeEvent("hello", hello);
eventTarget.attachEvent("hello", "Helo This is CustomEvent");
// removeEvent hello
// attachEvent hello
// hello event is not register

TIP

我们这里实现的事件,其实就是调用注册在事件函数栈中的回调函数

TIP

这里没有实现 once,也就是一次性事件

# 包装一个对象使其具有事件

function proxyCustomEvent(_eventObj) {
  _eventObj._listener = {};
  _eventObj.addEvent = (type, fn) => {
    if (!_eventObj._listener[type]) {
      _eventObj._listener[type] = [];
    }
    _eventObj._listener[type].push(fn);
  };
  _eventObj.attachEvent = (type, data) => {
    if (_eventObj._listener[type] && _eventObj._listener[type] instanceof Array) {
      if (_eventObj._listener[type].length) {
        _eventObj._listener[type].forEach((callback) => {
          callback(data);
        });
      } else {
        console.log(type, "event is not register");
      }
    }
  };
  _eventObj.removeEvent = (type, point) => {
    if (_eventObj._listener[type] && _eventObj._listener[type] instanceof Array) {
      let index = _eventObj._listener[type].findIndex((callback) => {
        return point === callback;
      });
      if (index > -1) {
        _eventObj._listener[type].splice(index, 1);
        point = null;
      }
    }
  };
  return _eventObj;
}

示例

let obj = {};

obj = proxyCustomEvent(obj);

function test(e) {
  console.log("test", e);
}
obj.addEvent("test", test);
obj.attachEvent("test", "proxyEvent");
// test proxyEvent
obj.removeEvent("test", test);
obj.attachEvent("test", "proxyEvent");
// test event is not register

# 模块化导出自定义事件类

function E() {}

E.prototype = {
  on: function (name, callback, ctx) {
    var e = this.e || (this.e = {});

    (e[name] || (e[name] = [])).push({
      fn: callback,
      ctx: ctx
    });

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

  emit: function (name) {
    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]);
      }
    }

    liveEvents.length ? (e[name] = liveEvents) : delete e[name];

    return this;
  }
};

module.exports = E;
module.exports.TinyEmitter = E;

使用时让我们的类继承这个类即可

TIP

注意,由于这里我们使用的是 JS 触发的事件,因此我们的事件绑定函数是同步执行的。

# 学习资料

Event (opens new window)
EventTarget (opens new window)
Github - tiny-emitter (opens new window)
Github - mitt (opens new window)