侧边栏壁纸
  • 累计撰写 47 篇文章
  • 累计创建 2 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

发布订阅模式的实现

发布订阅模式在前端开发中非常常见,用于解耦组件间的通信。

适用于各种前端场景,如组件通信、状态同步、跨层级数据传递等。

基础实现
class EventEmitter {
  constructor() {
    this.events = {};
  }

  // 订阅事件
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
    return this; // 支持链式调用
  }

  // 发布事件
  emit(eventName, ...args) {
    const callbacks = this.events[eventName];
    if (!callbacks || callbacks.length === 0) {
      return false;
    }
    
    callbacks.forEach(callback => {
      try {
        callback.apply(this, args);
      } catch (error) {
        console.error(`Error executing callback for event "${eventName}":`, error);
      }
    });
    
    return true;
  }

  // 取消订阅
  off(eventName, callback) {
    const callbacks = this.events[eventName];
    if (!callbacks) {
      return this;
    }
    
    if (!callback) {
      // 如果没有提供具体回调,则移除该事件的所有监听器
      delete this.events[eventName];
    } else {
      // 移除指定的回调
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
      
      // 如果没有监听器了,删除该事件
      if (callbacks.length === 0) {
        delete this.events[eventName];
      }
    }
    
    return this;
  }

  // 一次性订阅
  once(eventName, callback) {
    const onceWrapper = (...args) => {
      callback.apply(this, args);
      this.off(eventName, onceWrapper);
    };
    
    return this.on(eventName, onceWrapper);
  }
}
使用
// 创建事件中心
const eventBus = new EventEmitter();

// 订阅事件
eventBus.on('user-login', (userData) => {
  console.log('用户登录:', userData);
});

eventBus.on('user-login', (userData) => {
  console.log('更新用户界面:', userData.name);
});

// 发布事件
eventBus.emit('user-login', { 
  id: 1, 
  name: '张三', 
  email: 'zhangsan@example.com' 
});
// 输出:
// 用户登录: {id: 1, name: "张三", email: "zhangsan@example.com"}
// 更新用户界面: 张三

// 一次性订阅
eventBus.once('notification', (message) => {
  console.log('一次性通知:', message);
});

eventBus.emit('notification', '第一次触发'); // 会执行
eventBus.emit('notification', '第二次触发'); // 不会执行

// 取消订阅
const handleClick = (data) => console.log('点击:', data);
eventBus.on('button-click', handleClick);
eventBus.emit('button-click', '按钮1'); // 会执行

eventBus.off('button-click', handleClick);
eventBus.emit('button-click', '按钮2'); // 不会执行
TS 版
interface EventMap {
  [key: string]: (...args: any[]) => void;
}

class TypedEventEmitter<T extends EventMap> {
  private events: Partial<Record<keyof T, Function[]>> = {};

  on<K extends keyof T>(eventName: K, callback: T[K]) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName]!.push(callback);
    return this;
  }

  emit<K extends keyof T>(eventName: K, ...args: Parameters<T[K]>) {
    const callbacks = this.events[eventName];
    if (!callbacks) return false;
    
    callbacks.forEach(callback => callback(...args));
    return true;
  }
}

// 使用
interface MyEvents {
  'user-login': (user: { id: number; name: string }) => void;
  'data-loaded': (data: any[], timestamp: number) => void;
}

const emitter = new TypedEventEmitter<MyEvents>();

emitter.on('user-login', (user) => {
  console.log(user.name); // 类型安全
});

emitter.emit('user-login', { id: 1, name: '张三' });
注意事项

内存管理:及时移除不再需要的监听器,避免内存泄漏
错误处理:监听器中的错误应该被捕获,避免影响其他监听器
性能考虑:对于高频事件,考虑使用防抖或节流
调试友好:可以添加事件日志功能,便于调试
单例模式:通常全局使用一个事件总线实例即可

0

评论区