1273 字
6 分钟
JavaScript Event Bus
.png)
题目描述
实现一个 JavaScript 事件总线系统,要求:
- 支持事件订阅(
on
)、发布(emit
)、取消订阅(off
) - 支持一次性事件订阅(
once
) - 支持多事件通配符订阅(
*
) - 实现异步事件队列处理
- 支持事件作用域隔离
- 提供事件统计功能
要求完整实现事件总线逻辑,并通过测试用例验证。
解题思路
- 事件存储:使用 Map 存储事件类型与处理函数
- 通配符处理:实现
*
匹配所有事件类型 - 作用域隔离:支持创建独立的事件作用域
- 事件队列:使用 Promise 链实现异步顺序处理
- 统计分析:记录事件触发次数和执行时间
- 内存管理:提供清除所有事件的方法
关键洞察
- 发布订阅模式:实现组件间解耦通信的核心机制
- 通配符支持:
*
事件类型用于全局监听 - 异步队列:确保事件顺序执行,避免并行问题
- 作用域隔离:防止事件命名冲突,实现沙箱环境
- 一次性事件:通过包装函数自动取消订阅
- 性能监控:事件耗时统计帮助性能优化
代码流程
flowchart TD A[事件总线创建] --> B[事件存储] B --> C[/on/once 订阅/] C --> D[添加到事件队列] B --> E[/emit 发布/] E --> F[匹配处理函数] F --> G[异步队列执行] B --> H[/off 取消/] H --> I[从队列移除] J[通配符 *] --> K[全局事件处理]
代码实现
class EventBus { constructor() { // 使用Map存储事件类型和回调函数 this.events = new Map(); // 通配符事件存储 this.wildcardEvents = []; // 事件统计 this.stats = new Map(); // 异步队列 this.queue = Promise.resolve(); }
// 订阅事件 on(eventName, callback, scope = 'global') { const eventKey = `${scope}:${eventName}`;
if (!this.events.has(eventKey)) { this.events.set(eventKey, []); }
// 添加回调函数 this.events.get(eventKey).push(callback);
// 通配符处理 if (eventName === '*') { this.wildcardEvents.push({ callback, scope }); }
return this; }
// 一次性订阅 once(eventName, callback, scope = 'global') { const onceCallback = (...args) => { this.off(eventName, onceCallback, scope); callback(...args); }; return this.on(eventName, onceCallback, scope); }
// 发布事件 emit(eventName, data, scope = 'global') { const eventKey = `${scope}:${eventName}`; const startTime = performance.now();
// 创建异步任务 this.queue = this.queue.then(async () => { try { // 执行通配符事件 for (const { callback } of this.wildcardEvents) { await callback(eventName, data); }
// 执行匹配事件 if (this.events.has(eventKey)) { const callbacks = [...this.events.get(eventKey)]; for (const callback of callbacks) { await callback(data); } }
// 收集统计信息 const duration = performance.now() - startTime; this.recordStats(eventName, duration); } catch (error) { console.error(`EventBus error in ${eventName}:`, error); this.emit('error', error, 'system'); } });
return this; }
// 取消订阅 off(eventName, callbackToRemove, scope = 'global') { const eventKey = `${scope}:${eventName}`;
if (eventName === '*') { this.wildcardEvents = this.wildcardEvents.filter( ({ callback }) => callback !== callbackToRemove ); return this; }
if (!this.events.has(eventKey)) return this;
if (callbackToRemove) { const filteredCallbacks = this.events .get(eventKey) .filter(callback => callback !== callbackToRemove);
if (filteredCallbacks.length === 0) { this.events.delete(eventKey); } else { this.events.set(eventKey, filteredCallbacks); } } else { this.events.delete(eventKey); }
return this; }
// 创建作用域 createScopedBus(scopeName) { return { on: (event, callback) => this.on(event, callback, scopeName), once: (event, callback) => this.once(event, callback, scopeName), emit: (event, data) => this.emit(event, data, scopeName), off: (event, callback) => this.off(event, callback, scopeName) }; }
// 清除所有事件 clear() { this.events.clear(); this.wildcardEvents = []; return this; }
// 事件统计 recordStats(eventName, duration) { const stats = this.stats.get(eventName) || { count: 0, totalDuration: 0, avgDuration: 0 };
stats.count++; stats.totalDuration += duration; stats.avgDuration = stats.totalDuration / stats.count;
this.stats.set(eventName, stats); }
// 获取事件统计 getStats(eventName) { return this.stats.get(eventName) || null; }}
使用示例
// 创建事件总线const bus = new EventBus();
// 订阅全局事件bus.on('user.login', user = >{ console.log('User logged in:', user.name);});
// 订阅一次性事件bus.once('app.initialized', () = >{ console.log('App initialized!');});
// 通配符订阅所有事件bus.on('*', (event, data) = >{ console.log(` [$ { event }] triggered with: `, data);});
// 创建作用域事件总线const cartBus = bus.createScopedBus('cart');cartBus.on('item.added', item = >{ console.log('Item added to cart:', item.name);});
// 发布事件bus.emit('user.login', { name: 'Alice', id: 123});/*输出:[user.login] triggered with: {name: 'Alice', id: 123}User logged in: Alice*/
bus.emit('app.initialized');/*输出:[app.initialized] triggered with: undefinedApp initialized!*/
cartBus.emit('item.added', { name: 'Laptop', price: 999});/*输出:[cart:item.added] triggered with: {name: 'Laptop', price: 999}Item added to cart: Laptop*/
// 获取事件统计console.log(bus.getStats('user.login'));// { count: 1, totalDuration: 0.24, avgDuration: 0.24 }// 取消订阅bus.off('user.login');
业务场景
业务场景
- 跨组件通信:在大型应用中实现非父子组件间通信
- 插件系统:允许插件监听和触发核心应用事件
- 状态管理:作为轻量级状态管理解决方案
- 微前端架构:协调多个微应用间的通信
- 用户行为追踪:收集用户操作事件进行分析
- 实时通知系统:广播系统状态变更通知
架构优势:
- 解耦组件依赖,提高代码可维护性
- 支持异步事件处理,避免回调地狱
- 作用域隔离防止事件命名冲突
- 通配符监听简化全局事件处理
- 内置性能统计帮助优化关键路径
相似题目
- 实现观察者模式(中等) 核心:Subject/Observer 接口实现
- 实现状态管理库(困难) 核心:状态管理 + 响应式更新
- 实现 Promise 事件系统(中等) 核心:Promise 链式事件处理
- 实现浏览器事件委托(中等) 核心:事件冒泡 + 目标过滤
- 实现消息队列(困难) 核心:消息持久化 + 顺序保证