请用 JavaScript 实现一个事件触发器 (EventEmitter)。

嗨,各位代码世界的探险家们!准备好一起揭秘事件触发器的魔力了吗?

今天,咱们来聊聊一个在前端、后端甚至嵌入式开发中都非常常见的模式——事件触发器 (EventEmitter)。 想象一下,你正在玩一个超级玛丽的游戏,玛丽跳起来、吃到蘑菇、碰到敌人,这些都是游戏中的“事件”。 而事件触发器,就像一个可靠的信使,负责把这些事件通知给所有关心它们的人。

那么,什么是事件触发器? 简单来说,它就是一个对象,允许你:

  1. 注册 (on/addListener): 告诉它,我对某个事件感兴趣,当这个事件发生时,请通知我。
  2. 触发 (emit): 告诉所有人,某个事件已经发生了,顺便带上一些事件发生时的信息。
  3. 移除 (off/removeListener): 告诉它,我对某个事件不再感兴趣了,不用再通知我了。

准备好了吗? 让我们用 JavaScript 来实现一个简单的 EventEmitter!

1. EventEmitter 的骨架

首先,我们需要一个类来代表我们的 EventEmitter。 就像盖房子一样,先搭好框架:

class EventEmitter {
  constructor() {
    // 用来存储事件和对应的监听器(回调函数)
    this._events = {};
  }

  // 注册事件监听器
  on(event, listener) {
    // ... 稍后实现
  }

  // 触发事件
  emit(event, ...args) {
    // ... 稍后实现
  }

  // 移除事件监听器
  off(event, listener) {
    // ... 稍后实现
  }

  // 移除单个事件的所有监听器
  removeAllListeners(event) {
    // ... 稍后实现
  }

  // 获取指定事件的监听器数量
  listenerCount(event) {
    // ... 稍后实现
  }
}

现在,我们有了一个空的 EventEmitter 类,里面有几个核心的方法: on, emit, off, removeAllListeners, 和 listenerCount。 接下来,我们一块一块地把这些方法填满血肉。

2. 注册事件监听器: on 方法

on 方法负责把事件和监听器(也就是回调函数)关联起来。 想象一下,你订阅了一个新闻频道,当有新消息时,频道会通知你。 on 方法就是你的订阅操作。

class EventEmitter {
  constructor() {
    this._events = {};
  }

  on(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    if (!this._events[event]) {
      // 如果这个事件还没有人监听,就创建一个数组来存储监听器
      this._events[event] = [];
    }

    this._events[event].push(listener);
    return this; // 方便链式调用,比如 emitter.on('data', fn1).on('error', fn2)
  }

  // ... 其他方法稍后实现
}

解释一下:

  • 我们首先检查 listener 是否是一个函数,如果不是,就抛出一个错误。
  • 然后,我们检查 this._events 对象中是否已经存在 event 对应的数组。 如果没有,就创建一个新的空数组。
  • 最后,我们把 listener 添加到 event 对应的数组中。
  • return this 的目的是为了支持链式调用,让代码更优雅。

例子:

const emitter = new EventEmitter();

function handleData(data) {
  console.log('Received data:', data);
}

emitter.on('data', handleData); // 注册 data 事件的监听器
emitter.on('data', function(data) { // 也可以使用匿名函数
  console.log('Another handler for data:', data);
});

3. 触发事件: emit 方法

emit 方法负责触发事件,也就是通知所有注册过的监听器。 就像新闻频道发布了一条新消息,所有订阅了这个频道的人都会收到通知。

class EventEmitter {
  constructor() {
    this._events = {};
  }

  on(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    if (!this._events[event]) {
      this._events[event] = [];
    }

    this._events[event].push(listener);
    return this;
  }

  emit(event, ...args) {
    const listeners = this._events[event];

    if (listeners && listeners.length > 0) {
      // 复制监听器数组,避免在回调函数中修改监听器列表导致问题
      const listenersCopy = [...listeners];

      listenersCopy.forEach(listener => {
        try {
          listener(...args); // 调用监听器,并传递参数
        } catch (error) {
          console.error('Error in event listener:', error); // 捕获监听器中的错误
        }
      });
      return true; // 表示事件被成功触发
    }

    return false; // 表示没有监听器
  }

  // ... 其他方法稍后实现
}

解释一下:

  • 我们首先从 this._events 对象中获取 event 对应的监听器数组。
  • 如果存在监听器,我们就遍历这个数组,并依次调用每个监听器。
  • 在调用监听器时,我们使用 ...argsemit 方法接收到的参数传递给监听器。
  • 我们对监听器数组进行浅拷贝,避免在回调函数内部修改监听列表导致死循环或其它问题。
  • try...catch 语句用于捕获监听器执行过程中可能发生的错误,避免一个监听器的错误影响到其他监听器。
  • return true 表示事件被成功触发,return false 表示没有监听器。

例子:

const emitter = new EventEmitter();

emitter.on('data', function(data) {
  console.log('Received data:', data);
});

emitter.emit('data', 'Hello, world!'); // 触发 data 事件,并传递数据
emitter.emit('data', { message: 'This is an object.' }); // 触发 data 事件,并传递对象
emitter.emit('customEvent', 'This is a custom event'); //触发一个不存在监听器的事件

4. 移除事件监听器: off 方法

off 方法负责移除事件监听器,也就是取消订阅。 就像你不再想收到某个新闻频道的消息,你就取消订阅。

class EventEmitter {
  constructor() {
    this._events = {};
  }

  on(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    if (!this._events[event]) {
      this._events[event] = [];
    }

    this._events[event].push(listener);
    return this;
  }

  emit(event, ...args) {
    const listeners = this._events[event];

    if (listeners && listeners.length > 0) {
      const listenersCopy = [...listeners];
      listenersCopy.forEach(listener => {
        try {
          listener(...args);
        } catch (error) {
          console.error('Error in event listener:', error);
        }
      });
      return true;
    }

    return false;
  }

  off(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    const listeners = this._events[event];

    if (listeners && listeners.length > 0) {
      this._events[event] = listeners.filter(existingListener => existingListener !== listener);
      if (this._events[event].length === 0) {
        delete this._events[event]; // 如果没有监听器了,就删除这个事件
      }
      return true; // 表示成功移除
    }

    return false; // 表示没有找到监听器
  }

  // ... 其他方法稍后实现
}

解释一下:

  • 我们首先检查 listener 是否是一个函数。
  • 然后,我们从 this._events 对象中获取 event 对应的监听器数组。
  • 如果存在监听器,我们就使用 filter 方法创建一个新的数组,其中不包含要移除的 listener
  • 如果新的数组为空,就从 this._events 对象中删除 event 属性。
  • return true 表示成功移除,return false 表示没有找到监听器。

例子:

const emitter = new EventEmitter();

function handleData(data) {
  console.log('Received data:', data);
}

emitter.on('data', handleData);
emitter.emit('data', 'Hello, world!'); // 输出 "Received data: Hello, world!"

emitter.off('data', handleData); // 移除 handleData 监听器
emitter.emit('data', 'Hello, world!'); // 没有输出,因为监听器已经被移除

5. 移除单个事件的所有监听器: removeAllListeners 方法

这个方法比较简单,直接将事件对应的监听器数组清空或者直接删除事件即可。

class EventEmitter {
  constructor() {
    this._events = {};
  }

  on(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    if (!this._events[event]) {
      this._events[event] = [];
    }

    this._events[event].push(listener);
    return this;
  }

  emit(event, ...args) {
    const listeners = this._events[event];

    if (listeners && listeners.length > 0) {
      const listenersCopy = [...listeners];
      listenersCopy.forEach(listener => {
        try {
          listener(...args);
        } catch (error) {
          console.error('Error in event listener:', error);
        }
      });
      return true;
    }

    return false;
  }

  off(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    const listeners = this._events[event];

    if (listeners && listeners.length > 0) {
      this._events[event] = listeners.filter(existingListener => existingListener !== listener);
      if (this._events[event].length === 0) {
        delete this._events[event];
      }
      return true;
    }

    return false;
  }

  removeAllListeners(event) {
    if (event) {
      // 只移除指定事件的所有监听器
      delete this._events[event];
    } else {
      // 移除所有事件的所有监听器
      this._events = {};
    }
    return this; // 支持链式调用
  }

  // ... 其他方法稍后实现
}

例子:

const emitter = new EventEmitter();

function handleData(data) {
  console.log('Received data:', data);
}

function handleData2(data) {
  console.log('Received data 2:', data);
}

emitter.on('data', handleData);
emitter.on('data', handleData2);
emitter.emit('data', 'Hello, world!');

emitter.removeAllListeners('data');
emitter.emit('data', 'Hello, world!'); // 没有输出,因为监听器都被移除

6. 获取指定事件的监听器数量: listenerCount 方法

这个方法也很简单,直接返回事件对应的监听器数组的长度即可。

class EventEmitter {
  constructor() {
    this._events = {};
  }

  on(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    if (!this._events[event]) {
      this._events[event] = [];
    }

    this._events[event].push(listener);
    return this;
  }

  emit(event, ...args) {
    const listeners = this._events[event];

    if (listeners && listeners.length > 0) {
      const listenersCopy = [...listeners];
      listenersCopy.forEach(listener => {
        try {
          listener(...args);
        } catch (error) {
          console.error('Error in event listener:', error);
        }
      });
      return true;
    }

    return false;
  }

  off(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    const listeners = this._events[event];

    if (listeners && listeners.length > 0) {
      this._events[event] = listeners.filter(existingListener => existingListener !== listener);
      if (this._events[event].length === 0) {
        delete this._events[event];
      }
      return true;
    }

    return false;
  }

  removeAllListeners(event) {
    if (event) {
      delete this._events[event];
    } else {
      this._events = {};
    }
    return this;
  }

  listenerCount(event) {
    const listeners = this._events[event];
    return listeners ? listeners.length : 0;
  }
}

例子:

const emitter = new EventEmitter();

function handleData(data) {
  console.log('Received data:', data);
}

emitter.on('data', handleData);
emitter.on('data', function(data) {
  console.log('Another handler for data:', data);
});

console.log('Number of data listeners:', emitter.listenerCount('data')); // 输出 2

emitter.off('data', handleData);

console.log('Number of data listeners:', emitter.listenerCount('data')); // 输出 1

7. 完整的 EventEmitter 代码

现在,我们把所有的代码片段组合起来,就得到了一个完整的 EventEmitter 实现:

class EventEmitter {
  constructor() {
    this._events = {};
  }

  on(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    if (!this._events[event]) {
      this._events[event] = [];
    }

    this._events[event].push(listener);
    return this;
  }

  emit(event, ...args) {
    const listeners = this._events[event];

    if (listeners && listeners.length > 0) {
      const listenersCopy = [...listeners];
      listenersCopy.forEach(listener => {
        try {
          listener(...args);
        } catch (error) {
          console.error('Error in event listener:', error);
        }
      });
      return true;
    }

    return false;
  }

  off(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

    const listeners = this._events[event];

    if (listeners && listeners.length > 0) {
      this._events[event] = listeners.filter(existingListener => existingListener !== listener);
      if (this._events[event].length === 0) {
        delete this._events[event];
      }
      return true;
    }

    return false;
  }

  removeAllListeners(event) {
    if (event) {
      delete this._events[event];
    } else {
      this._events = {};
    }
    return this;
  }

  listenerCount(event) {
    const listeners = this._events[event];
    return listeners ? listeners.length : 0;
  }
}

8. EventEmitter 的应用场景

EventEmitter 在各种场景中都有广泛的应用,例如:

场景 描述 例子
前端开发 用于组件之间的通信,比如一个组件触发了一个事件,另一个组件监听这个事件并做出相应的处理。 React/Vue/Angular 等框架中的组件通信机制。
后端开发 (Node.js) 用于处理异步事件,比如服务器接收到客户端的请求,或者从数据库中读取数据。 Node.js 的 http 模块、fs 模块、stream 模块都使用了 EventEmitter。 例如, http.Server 对象会触发 request 事件, fs.ReadStream 对象会触发 data 事件、end 事件、error 事件。
状态管理 用于管理应用的状态,比如当状态发生变化时,触发一个事件,通知所有关心这个状态的组件。 Redux、Vuex 等状态管理库。
物联网 (IoT) 用于处理传感器数据,比如当传感器检测到温度变化时,触发一个事件,通知其他设备。 各种物联网平台和设备。
游戏开发 用于处理游戏中的各种事件,比如玩家移动、攻击、死亡等。 各种游戏引擎。

9. 总结

今天,我们一起实现了一个简单的 EventEmitter,并了解了它的核心方法和应用场景。 希望通过今天的讲解,你对事件触发器有了更深入的理解。

重点回顾:

  • on(event, listener): 注册事件监听器。
  • emit(event, ...args): 触发事件,并传递参数。
  • off(event, listener): 移除事件监听器。
  • removeAllListeners(event): 移除某个事件的所有监听器。
  • listenerCount(event): 获取某个事件的监听器数量。

事件触发器是一种非常强大的设计模式,可以帮助我们构建松耦合、可扩展的应用程序。 熟练掌握它,你的代码将会更加灵活和易于维护。

希望今天的讲座对你有所帮助! 如果你还有任何问题,欢迎提问。 祝你在代码世界里探索愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注