前言 最近开始想写这个系列文章,一方面是自己刚开始学编程的时候,也花了比较多的时间去学习各种框架的API,但却不了解内部的运行原理,即使了解也是通过各种文章和博客泛泛的了解一下。但从后面的经历来看,想快速的掌握一个框架,尝试自己实现一个demo版,会有奇效。希望结合自己的描述,能让新人少走一些弯路。另一方面,一些同学对于 “看源码” 有着一点崇拜但抗拒的情绪。虽然大家都说可以从 0.x 版本看起,可以减少阅读成本。但其实对于一些同学门槛还是高了一点,这个系列期望能用更精简和可读的代码介绍各个轮子的原理。

接口设计

这期我们要实现的轮子是一个发布订阅的事件库,对标Events。我写代码一般习惯写之前先想好输入和输出,以及写好的代码会怎么用。发布订阅这种模型里面,两个核心的API on 和 emit ,通常用法如下:

  1. var ee = new EventEmitter();
  2. ee.on('message', function (text) {
  3. console.log(text);
  4. });
  5. ee.emit('message', 'hello world 1');

订阅一类消息和触发它。除了这两个核心API,我们还会实现一些周边的便捷实用的API

  • addEventListener(event, listener) 为指定的事件注册一个监听器
  • emit(event) 触发指定事件
  • on(event, listener) 和addEventListener一致,只是别名
  • removeListener(event, listener): 移除指定事件的监听器
  • removeAllListeners([event]):移除指定事件的所有监听回调
  • once(event, listener): 和on类似,但只触发一次


第一版实现

有了接口设计和使用方法,可以开始造轮子了。先初始化一下代码

  1. class EventEmitter {
  2. on(event, listener) {
  3. }
  4. emit(event, params) {
  5. }
  6. }
  7. export default EventEmitter

先写两个核心的API。我们要监听事件和触发事件,总要有个地方存吧,JS里面能存数据的结构无非是数组和对象,而我们的事件是有名字的,那就先用对象。在监听事件的时候存进去,触发的事件调用一下

  1. class EventEmitter {
  2. listeners = {};
  3. on(event, listener) {
  4. this.listeners[event] = listener;
  5. }
  6. emit(event, params) {
  7. this.listeners[event](params);
  8. }
  9. }

跑一下我们最开始提的使用方法,一行醒目的 hello world 打印了出来。一个最小化demo就已经完成了,总共9行代码

完善

不知道看完上面代码的同学有没有一种 “我又能行了” 的感觉😂。在这个基础上我们继续完善一下。在实际使用过程中,不管是DOM还是Node.js都支持同一个事件有多个监听器,比如这样

  1. var ee = new EventEmitter();
  2. ee.on('message', function (text) {
  3. console.log('第一次监听')
  4. console.log(text);
  5. });
  6. ee.on('message', function (text) {
  7. console.log('第二次监听')
  8. console.log(text);
  9. });
  10. ee.emit('message', 'hello world 1');

这个难不倒我们,上面只存了一个listener,支持存多个listener就好了。数据结构改一下,变成数组:

  1. class EventEmitter {
  2. listeners = {};
  3. on(event, listener) {
  4. if (this.listeners[event]) {
  5. this.listeners[event].push(listener)
  6. } else {
  7. this.listeners[event] = [listener]
  8. }
  9. }
  10. emit(event, params) {
  11. if (!this.listeners[event]) return;
  12. this.listeners[event].forEach((listener) => {
  13. listener(params)
  14. })
  15. }
  16. }

触发事件时也调整为了依次调用监听器。
支持一下 addEventListener 方法:

  1. addEventListener(event, listener) {
  2. this.on(event, listener);
  3. }

有了上面的思路,清除所有监听器也是水到渠成。直接delete

  1. removeAllListeners(event) {
  2. if (!this.listeners[event]) return;
  3. delete this.listeners[event];
  4. }

清除特定的 listener

  1. removeListeners(event, listener) {
  2. if (!this.listeners[event]) return;
  3. const index = this.listeners[event].indexOf(listener);
  4. this.listeners[event].splice(index, 1)
  5. }

只触发一次的once: 触发过后就解除监听

  1. once(event, listener) {
  2. let self = this
  3. this.on(event, function () {
  4. let args = Array.prototype.slice.call(arguments);
  5. listener.apply(null, args);
  6. self.removeListeners(event, listener)
  7. })
  8. }

完整代码:源码地址

  1. class EventEmitter {
  2. listeners = {};
  3. on(event, listener) {
  4. if (this.listeners[event]) {
  5. this.listeners[event].push(listener)
  6. } else {
  7. this.listeners[event] = [listener]
  8. }
  9. }
  10. emit(event, params?) {
  11. if (!this.listeners[event]) return;
  12. this.listeners[event].forEach((listener) => {
  13. listener(params)
  14. })
  15. }
  16. addEventListener(event, listener) {
  17. this.on(event, listener);
  18. }
  19. removeAllListeners(event) {
  20. if (!this.listeners[event]) return;
  21. delete this.listeners[event];
  22. }
  23. removeListeners(event, listener) {
  24. if (!this.listeners[event]) return;
  25. const index = this.listeners[event].indexOf(listener);
  26. this.listeners[event].splice(index, 1)
  27. }
  28. once(event, listener) {
  29. let self = this
  30. this.on(event, function () {
  31. let args = Array.prototype.slice.call(arguments);
  32. listener.apply(null, args);
  33. self.removeListeners(event, listener)
  34. })
  35. }
  36. }
  37. export default EventEmitter

小结

一个学习使用的小demo到这里就完成了,生产使用的源码会复杂得多,包含了各种各样的边际情况和异常处理,打印出开发易于阅读的堆栈提示。但其中的核心代码都是类似的