秋招字节前端手写事件订阅模块

40 阅读1分钟

实现简单的事件订阅模块,核心的onemitoff实现即可

最强前端学习资料|已过字节、阿里、百度等大厂,一站式前端知识库,免费开源,辛苦Star


function Event() {
  // 键=事件名(字符串),值=回调函数数组
  const events = new Map();

  return {
    on(eventName, callback) {
      if (typeof eventName !== 'string' || eventName.trim() === '') {
        throw new Error('事件名必须是非空字符串');
      }
      if (typeof callback !== 'function') {
        throw new Error('回调函数必须是Function类型');
      }

      if (!events.has(eventName)) {
        events.set(eventName, []);
      }
      events.get(eventName).push(callback);
    },

    emit(eventName, data) {
      // 事件不存在则直接返回
      if (!events.has(eventName)) return;

      // 遍历回调列表执行(浅拷贝数组,避免执行中修改列表导致异常)
      const callbacks = [...events.get(eventName)];
      callbacks.forEach((callback) => {
        // 容错:防止个别回调报错导致后续回调无法执行
        try {
          callback(data);
        } catch (error) {
          console.error(`事件${eventName}的回调执行失败:`, error);
        }
      });
    },

    off(eventName, callback) {
      if (!events.has(eventName)) return;

      const callbacks = events.get(eventName);
      if (typeof callback === 'function') {
        // 若传入回调,过滤掉匹配的函数
        const newCallbacks = callbacks.filter((cb) => cb !== callback);
        if (newCallbacks.length > 0) {
          events.set(eventName, newCallbacks);
        } else {
          // 过滤后无回调,删除该事件(释放内存)
          events.delete(eventName);
        }
      } else if (callback === undefined) {
        // 若未传入回调,直接删除整个事件(取消所有订阅)
        events.delete(eventName);
      } else {
        throw new Error('取消订阅时,回调参数必须是Function或undefined');
      }
    },

    /**
     * @param {string} eventName - 事件名称
     * @returns {boolean} 是否存在订阅
     */
    has(eventName) {
      return events.has(eventName);
    },

    /**
     * @param {string} eventName - 事件名称
     * @returns {number} 订阅数量(事件不存在则返回0)
     */
    getListenerCount(eventName) {
      return events.has(eventName) ? events.get(eventName).length : 0;
    }
  };
}


const myEvent = Event();

function handleMessage(data) {
  console.log('收到消息:', data);
}

myEvent.on('message', handleMessage);
myEvent.on('message', (data) => console.log('匿名回调收到:', data));

myEvent.emit('message', 'Hello EventEmitter!');

console.log('message事件订阅数:', myEvent.getListenerCount('message')); // 输出:2

myEvent.off('message', handleMessage);
myEvent.emit('message', '再次触发');

myEvent.off('message');
console.log('message事件是否存在:', myEvent.has('message')); 
myEvent.emit('message', '不会触发');