1273 字
6 分钟
JavaScript Event Bus

题目描述#

实现一个 JavaScript 事件总线系统,要求:

  1. 支持事件订阅(on)、发布(emit)、取消订阅(off)
  2. 支持一次性事件订阅(once)
  3. 支持多事件通配符订阅(*)
  4. 实现异步事件队列处理
  5. 支持事件作用域隔离
  6. 提供事件统计功能

要求完整实现事件总线逻辑,并通过测试用例验证。

解题思路#

  1. 事件存储:使用 Map 存储事件类型与处理函数
  2. 通配符处理:实现 * 匹配所有事件类型
  3. 作用域隔离:支持创建独立的事件作用域
  4. 事件队列:使用 Promise 链实现异步顺序处理
  5. 统计分析:记录事件触发次数和执行时间
  6. 内存管理:提供清除所有事件的方法

关键洞察#

  1. 发布订阅模式:实现组件间解耦通信的核心机制
  2. 通配符支持* 事件类型用于全局监听
  3. 异步队列:确保事件顺序执行,避免并行问题
  4. 作用域隔离:防止事件命名冲突,实现沙箱环境
  5. 一次性事件:通过包装函数自动取消订阅
  6. 性能监控:事件耗时统计帮助性能优化

代码流程#

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: undefined
App 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');

业务场景#

业务场景#

  1. 跨组件通信:在大型应用中实现非父子组件间通信
  2. 插件系统:允许插件监听和触发核心应用事件
  3. 状态管理:作为轻量级状态管理解决方案
  4. 微前端架构:协调多个微应用间的通信
  5. 用户行为追踪:收集用户操作事件进行分析
  6. 实时通知系统:广播系统状态变更通知

架构优势

  • 解耦组件依赖,提高代码可维护性
  • 支持异步事件处理,避免回调地狱
  • 作用域隔离防止事件命名冲突
  • 通配符监听简化全局事件处理
  • 内置性能统计帮助优化关键路径

相似题目#

  1. 实现观察者模式(中等) 核心:Subject/Observer 接口实现
  2. 实现状态管理库(困难) 核心:状态管理 + 响应式更新
  3. 实现 Promise 事件系统(中等) 核心:Promise 链式事件处理
  4. 实现浏览器事件委托(中等) 核心:事件冒泡 + 目标过滤
  5. 实现消息队列(困难) 核心:消息持久化 + 顺序保证
JavaScript Event Bus
https://website-truelovings-projects.vercel.app/posts/code/javascript/javascript-event-bus/
作者
欢迎来到StarSky的网站!
发布于
2025-08-22
许可协议
CC BY-NC-SA 4.0