JavaScript 发布订阅模式实现详解

发布订阅模式(Pub/Sub)是 JavaScript 中一种非常重要的设计模式,它广泛应用于事件处理、消息通信等场景。本文将详细介绍发布订阅模式的原理、实现方式以及实际应用案例。

什么是发布订阅模式?

发布订阅模式是一种消息范式,消息的发送者(发布者)不会直接将消息发送给特定的接收者(订阅者),而是通过消息通道(事件中心)进行广播,订阅者可以接收到自己感兴趣的消息。

核心概念:

  • 发布者(Publisher):发出事件/消息的对象
  • 订阅者(Subscriber):监听特定事件/消息的对象
  • 事件中心(Event Channel):管理事件与订阅者之间的关系

基础实现

1. 最简单的发布订阅实现

class EventEmitter {
  constructor() {
    this.events = {}; // 存储事件及回调
  }

  // 订阅事件
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  }

  // 发布事件
  emit(eventName, ...args) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(callback => {
        callback(...args);
      });
    }
  }

  // 取消订阅
  off(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(
        cb => cb !== callback
      );
    }
  }

  // 一次性订阅
  once(eventName, callback) {
    const onceCallback = (...args) => {
      callback(...args);
      this.off(eventName, onceCallback);
    };
    this.on(eventName, onceCallback);
  }
}

// 使用示例
const eventBus = new EventEmitter();

// 订阅事件
eventBus.on('message', (msg) => {
  console.log('收到消息:', msg);
});

// 发布事件
eventBus.emit('message', 'Hello World!'); // 输出: 收到消息: Hello World!

// 一次性订阅
eventBus.once('onceEvent', (msg) => {
  console.log('一次性事件:', msg);
});

eventBus.emit('onceEvent', '第一次触发'); // 输出: 一次性事件: 第一次触发
eventBus.emit('onceEvent', '第二次触发'); // 无输出

// 取消订阅
const callback = (msg) => {
  console.log('将被取消的订阅:', msg);
};
eventBus.on('cancelEvent', callback);
eventBus.emit('cancelEvent', '测试取消前'); // 输出: 将被取消的订阅: 测试取消前
eventBus.off('cancelEvent', callback);
eventBus.emit('cancelEvent', '测试取消后'); // 无输出

高级功能实现

2. 支持命名空间的发布订阅

class AdvancedEventEmitter {
  constructor() {
    this.events = {};
    this.maxListeners = 10; // 默认最大监听器数量
  }

  // 添加监听器
  on(eventName, listener) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }

    // 检查监听器数量限制
    if (this.events[eventName].length >= this.maxListeners) {
      console.warn(`Possible memory leak detected. ${this.events[eventName].length} ${eventName} listeners added.`);
    }

    this.events[eventName].push(listener);
    return this; // 支持链式调用
  }

  // 触发事件
  emit(eventName, ...args) {
    if (this.events[eventName]) {
      const listeners = this.events[eventName].slice(); // 创建副本,避免回调中取消订阅导致的问题
      listeners.forEach(listener => {
        try {
          listener.apply(this, args);
        } catch (err) {
          console.error(`Error in event listener for ${eventName}:`, err);
        }
      });
    }
    return this;
  }

  // 取消订阅
  off(eventName, listener) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(
        l => l !== listener
      );
    }
    return this;
  }

  // 一次性订阅
  once(eventName, listener) {
    const onceWrapper = (...args) => {
      listener.apply(this, args);
      this.off(eventName, onceWrapper);
    };
    this.on(eventName, onceWrapper);
    return this;
  }

  // 设置最大监听器数量
  setMaxListeners(n) {
    this.maxListeners = n;
    return this;
  }

  // 移除所有监听器或指定事件的所有监听器
  removeAllListeners(eventName) {
    if (eventName) {
      delete this.events[eventName];
    } else {
      this.events = {};
    }
    return this;
  }

  // 获取指定事件的监听器数量
  listenerCount(eventName) {
    return this.events[eventName] ? this.events[eventName].length : 0;
  }
}

// 使用示例
const advancedBus = new AdvancedEventEmitter();

// 设置最大监听器数量
advancedBus.setMaxListeners(2);

// 添加多个监听器
advancedBus.on('data', (d) => console.log('Data 1:', d));
advancedBus.on('data', (d) => console.log('Data 2:', d));
advancedBus.on('data', (d) => console.log('Data 3:', d)); // 会收到警告

// 触发事件
advancedBus.emit('data', { id: 1, value: 'test' });
/*
输出:
Data 1: {id: 1, value: "test"}
Data 2: {id: 1, value: "test"}
Data 3: {id: 1, value: "test"}
*/

// 获取监听器数量
console.log(advancedBus.listenerCount('data')); // 3

// 移除所有监听器
advancedBus.removeAllListeners('data');
console.log(advancedBus.listenerCount('data')); // 0

3. 支持异步事件处理的发布订阅

class AsyncEventEmitter {
  constructor() {
    this.events = {};
    this.asyncEvents = {};
  }

  // 同步订阅
  on(eventName, listener) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(listener);
    return this;
  }

  // 异步订阅
  onAsync(eventName, listener) {
    if (!this.asyncEvents[eventName]) {
      this.asyncEvents[eventName] = [];
    }
    this.asyncEvents[eventName].push(listener);
    return this;
  }

  // 触发事件(同步)
  emitSync(eventName, ...args) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(listener => {
        listener(...args);
      });
    }
    return this;
  }

  // 触发事件(异步)
  async emit(eventName, ...args) {
    // 先执行同步监听器
    if (this.events[eventName]) {
      this.events[eventName].forEach(listener => {
        listener(...args);
      });
    }

    // 然后执行异步监听器
    if (this.asyncEvents[eventName]) {
      for (const listener of this.asyncEvents[eventName]) {
        await listener(...args);
      }
    }
    return this;
  }

  // 取消订阅
  off(eventName, listener) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(
        l => l !== listener
      );
    }
    if (this.asyncEvents[eventName]) {
      this.asyncEvents[eventName] = this.asyncEvents[eventName].filter(
        l => l !== listener
      );
    }
    return this;
  }
}

// 使用示例
const asyncBus = new AsyncEventEmitter();

// 同步订阅
asyncBus.on('syncEvent', (msg) => {
  console.log('同步处理:', msg);
});

// 异步订阅
asyncBus.onAsync('asyncEvent', async (msg) => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log('异步处理1:', msg);
});

asyncBus.onAsync('asyncEvent', async (msg) => {
  await new Promise(resolve => setTimeout(resolve, 500));
  console.log('异步处理2:', msg);
});

// 触发同步事件
console.log('开始同步事件');
asyncBus.emitSync('syncEvent', '同步消息');
console.log('同步事件结束');

// 触发异步事件
console.log('开始异步事件');
asyncBus.emit('asyncEvent', '异步消息').then(() => {
  console.log('所有异步处理完成');
});

/*
输出顺序:
开始同步事件
同步处理: 同步消息
同步事件结束
开始异步事件
(等待500ms)
异步处理2: 异步消息
(等待500ms,总共1s)
异步处理1: 异步消息
所有异步处理完成
*/

实际应用场景

1. Vue 中的事件总线

// eventBus.js
class EventBus {
  constructor() {
    this.events = {};
  }

  $on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  $emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(callback => {
        callback(...args);
      });
    }
  }

  $off(event, callback) {
    if (!this.events[event]) return;
    if (!callback) {
      delete this.events[event];
    } else {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
  }
}

// 创建全局事件总线
const bus = new EventBus();
export default bus;

// 组件A.vue - 发布事件
import bus from './eventBus';
bus.$emit('user-logged-in', { userId: 123 });

// 组件B.vue - 订阅事件
import bus from './eventBus';
bus.$on('user-logged-in', (user) => {
  console.log('用户登录:', user);
});

2. React 中的跨组件通信

// EventService.js
class EventService {
  constructor() {
    this.listeners = {};
  }

  subscribe(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    const listenerId = Symbol();
    this.listeners[event].push({ id: listenerId, callback });
    return listenerId;
  }

  publish(event, data) {
    if (!this.listeners[event]) return;
    this.listeners[event].forEach(listener => {
      listener.callback(data);
    });
  }

  unsubscribe(event, listenerId) {
    if (!this.listeners[event]) return;
    this.listeners[event] = this.listeners[event].filter(
      listener => listener.id !== listenerId
    );
  }
}

const eventService = new EventService();
export default eventService;

// ComponentA.jsx - 发布事件
import React from 'react';
import eventService from './EventService';

const ComponentA = () => {
  const handleClick = () => {
    eventService.publish('button-clicked', { time: new Date() });
  };

  return <button onClick={handleClick}>点击我</button>;
};

// ComponentB.jsx - 订阅事件
import React, { useEffect, useState } from 'react';
import eventService from './EventService';

const ComponentB = () => {
  const [lastClick, setLastClick] = useState(null);

  useEffect(() => {
    const listenerId = eventService.subscribe('button-clicked', data => {
      setLastClick(data.time);
    });

    return () => {
      eventService.unsubscribe('button-clicked', listenerId);
    };
  }, []);

  return (
    <div>
      最后一次点击时间: {lastClick ? lastClick.toString() : '暂无'}
    </div>
  );
};

性能优化与注意事项

  1. 内存管理

    • 及时取消不再需要的事件监听
    • 避免在短时间内大量添加监听器
    • 设置合理的最大监听器数量
  2. 错误处理

    • 监听器内部应该做好错误捕获
    • 可以考虑添加全局错误处理回调
  3. 性能优化

    • 对于高频事件,可以考虑使用防抖或节流
    • 避免在监听器中执行耗时操作
  4. 扩展功能

    • 添加事件过滤器
    • 支持事件优先级
    • 添加中间件机制

完整实现代码

下面是一个功能完整的发布订阅实现,包含了上述所有优化点:

class UltimateEventEmitter {
  constructor(options = {}) {
    this._events = {};
    this._maxListeners = options.maxListeners || 10;
    this._middlewares = options.middlewares || [];
    this._errorHandler = options.errorHandler || console.error;
  }

  // 添加中间件
  use(middleware) {
    this._middlewares.push(middleware);
    return this;
  }

  // 应用中间件
  _applyMiddlewares(eventName, data) {
    return this._middlewares.reduce((result, middleware) => {
      try {
        return middleware(eventName, result) || result;
      } catch (err) {
        this._errorHandler(`Middleware error for event "${eventName}":`, err);
        return result;
      }
    }, data);
  }

  // 添加事件监听
  on(eventName, listener, options = {}) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function');
    }

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

    // 检查监听器数量限制
    if (this._events[eventName].length >= this._maxListeners) {
      console.warn(`Possible memory leak detected. ${this._events[eventName].length} ${eventName} listeners added.`);
    }

    const listenerObj = {
      listener,
      once: !!options.once,
      priority: options.priority || 0,
      context: options.context || this
    };

    // 按优先级插入
    const listeners = this._events[eventName];
    let i = listeners.length;
    while (i > 0 && listeners[i - 1].priority > listenerObj.priority) {
      i--;
    }
    listeners.splice(i, 0, listenerObj);

    return this;
  }

  // 一次性监听
  once(eventName, listener, options = {}) {
    return this.on(eventName, listener, { ...options, once: true });
  }

  // 触发事件
  emit(eventName, ...args) {
    if (!this._events[eventName]) return false;

    // 应用中间件
    let processedArgs = args;
    try {
      processedArgs = this._applyMiddlewares(eventName, args);
    } catch (err) {
      this._errorHandler(`Middleware processing error for event "${eventName}":`, err);
      return false;
    }

    // 创建监听器副本并排序
    const listeners = this._events[eventName].slice().sort((a, b) => b.priority - a.priority);

    let result;
    for (let i = 0; i < listeners.length; i++) {
      const { listener, once, context } = listeners[i];
      
      try {
        const listenerResult = listener.apply(context, processedArgs);
        // 如果某个监听器返回false,则停止后续监听器执行
        if (listenerResult === false) {
          result = false;
          break;
        }
        result = result || listenerResult;
      } catch (err) {
        this._errorHandler(`Error in listener for event "${eventName}":`, err);
      }

      // 一次性监听器移除
      if (once) {
        this.off(eventName, listener);
      }
    }

    return result;
  }

  // 异步触发事件
  async emitAsync(eventName, ...args) {
    if (!this._events[eventName]) return;

    // 应用中间件
    let processedArgs = args;
    try {
      processedArgs = await this._applyMiddlewares(eventName, args);
    } catch (err) {
      this._errorHandler(`Middleware processing error for event "${eventName}":`, err);
      return;
    }

    // 创建监听器副本并排序
    const listeners = this._events[eventName].slice().sort((a, b) => b.priority - a.priority);

    for (let i = 0; i < listeners.length; i++) {
      const { listener, once, context } = listeners[i];
      
      try {
        await listener.apply(context, processedArgs);
      } catch (err) {
        this._errorHandler(`Error in async listener for event "${eventName}":`, err);
      }

      // 一次性监听器移除
      if (once) {
        this.off(eventName, listener);
      }
    }
  }

  // 移除事件监听
  off(eventName, listener) {
    if (!this._events[eventName]) return this;

    if (!listener) {
      delete this._events[eventName];
      return this;
    }

    this._events[eventName] = this._events[eventName].filter(
      l => l.listener !== listener
    );

    if (this._events[eventName].length === 0) {
      delete this._events[eventName];
    }

    return this;
  }

  // 移除所有监听器
  removeAllListeners(eventName) {
    if (eventName) {
      delete this._events[eventName];
    } else {
      this._events = {};
    }
    return this;
  }

  // 获取监听器数量
  listenerCount(eventName) {
    if (!eventName) {
      return Object.values(this._events).reduce(
        (count, listeners) => count + listeners.length, 0
      );
    }
    return this._events[eventName] ? this._events[eventName].length : 0;
  }

  // 设置最大监听器数量
  setMaxListeners(n) {
    if (typeof n !== 'number' || n < 0) {
      throw new TypeError('n must be a positive number');
    }
    this._maxListeners = n;
    return this;
  }

  // 获取所有事件名
  eventNames() {
    return Object.keys(this._events);
  }
}

// 使用示例
const emitter = new UltimateEventEmitter({
  maxListeners: 2,
  errorHandler: (err) => {
    console.error('Custom error handler:', err);
  }
});

// 添加中间件
emitter.use((eventName, data) => {
  console.log(`Middleware: event "${eventName}" with data`, data);
  if (eventName === 'filtered') {
    return null; // 过滤掉这个事件
  }
  return data.map(item => item * 2); // 修改数据
});

// 添加监听器
emitter.on('data', (num) => {
  console.log('Data received (normal priority):', num);
});

emitter.on('data', (num) => {
  console.log('Data received (high priority):', num);
}, { priority: 1 });

emitter.once('once', (msg) => {
  console.log('Once event:', msg);
});

// 触发事件
emitter.emit('data', 1, 2, 3);
/*
Middleware: event "data" with data [1, 2, 3]
Data received (high priority): [2, 4, 6]
Data received (normal priority): [2, 4, 6]
*/

emitter.emit('once', 'first time');
// Once event: first time
emitter.emit('once', 'second time'); // 不会触发

emitter.emit('filtered', 'should not appear'); // 被中间件过滤

总结

发布订阅模式是 JavaScript 中非常重要的设计模式,它能够实现组件间的松耦合通信。本文介绍了从基础到高级的多种实现方式,包括:

  1. 基础的事件发布订阅实现
  2. 支持命名空间和错误处理的高级实现
  3. 支持异步事件处理的实现
  4. 在实际框架(Vue/React)中的应用
  5. 完整的生产级实现

在实际开发中,可以根据项目需求选择合适的实现方式。对于简单的场景,基础实现就足够了;对于复杂的应用,建议使用功能更完整的实现或现有的成熟库(如 EventEmitter3、RxJS 等)。

关键点总结:

  • 发布订阅模式的核心是维护一个事件-监听器的映射关系
  • 需要注意内存管理和错误处理
  • 中间件机制可以增强事件处理能力
  • 异步事件处理在现代应用中越来越重要
  • 合理的优先级系统可以解决监听器执行顺序问题
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北辰alk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值