JavaScript class中实现观察者模式需封装事件管理逻辑:用Map存储事件名与回调数组,提供subscribe、unsubscribe和notify方法;支持once选项和自动清理;推荐组合式集成而非继承;注意内存泄漏、错误隔离和命名规范。

在 JavaScript class 中实现观察者(订阅-发布)模式,核心是让类内部维护一个事件监听器列表,提供 subscribe(订阅)、unsubscribe(取消订阅)和 notify(发布/触发)方法。不需要依赖外部库,用原生 JS 即可完成。
1. 基础实现:在类中封装事件管理逻辑
最简方式是在类中用 Map 或普通对象存储事件名与回调函数数组的映射关系:
class EventEmitter {
constructor() {
this.events = new Map(); // 事件名 → 回调函数数组
}
subscribe(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
const listeners = this.events.get(event);
listeners.push(callback);
}
unsubscribe(event, callback) {
if (!this.events.has(event)) return;
const listeners = this.events.get(event);
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
}
}
notify(event, data) {
if (!this.events.has(event)) return;
const listeners = this.events.get(event);
// 浅拷贝避免执行过程中被修改影响遍历
[...listeners].forEach(cb => cb(data));
}
}
2. 支持一次性订阅与自动清理(增强实用性)
实际项目中常需支持「只触发一次」或「实例销毁时自动退订」,可在 subscribe 返回一个取消函数:
- 返回的取消函数既能手动解绑,也便于配合
AbortController或生命周期管理 - 支持
{ once: true }选项,简化一次性监听
class EventEmitter {
constructor() {
this.events = new Map();
}
subscribe(event, callback, options = {}) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
const listeners = this.events.get(event);
const wrapped = (...args) => {
callback(...args);
if (options.once) {
this.unsubscribe(event, wrapped);
}
};
listeners.push(wrapped);
return () => this.unsubscribe(event, wrapped);
}
unsubscribe(event, callback) {
if (!this.events.has(event)) return;
const listeners = this.events.get(event);
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
}
}
notify(event, ...args) {
if (!this.events.has(event)) return;
const listeners = this.events.get(event);
[...listeners].forEach(cb => cb(...args));
}
}
使用示例:
立即学习“Java免费学习笔记(深入)”;
const bus = new EventEmitter();
// 订阅并获取取消函数
const off = bus.subscribe('user-login', (user) => {
console.log('欢迎', user.name);
});
bus.notify('user-login', { name: 'Alice' }); // ✅ 触发
off(); // ? 手动取消
bus.notify('user-login', { name: 'Bob' }); // ❌ 不再触发
3. 在业务类中集成观察者能力(推荐做法)
不建议所有类都重复写事件逻辑,可用组合或继承方式复用。更现代、低侵入的做法是「组合」:
- 单独定义
EventEmitter类(如上) - 在业务类中实例化它,把事件委托给它管理
- 避免继承带来的耦合,也方便单元测试和替换
class UserService {
constructor() {
this.emitter = new EventEmitter();
}
login(user) {
// ... 登录逻辑
this.emitter.notify('login-success', user);
this.emitter.notify('user-updated', user);
}
onLoginSuccess(callback) {
return this.emitter.subscribe('login-success', callback);
}
onUserUpdated(callback) {
return this.emitter.subscribe('user-updated', callback);
}
}
// 使用
const service = new UserService();
service.onLoginSuccess(u => console.log('登录成功:', u));
service.login({ id: 1, name: 'Tom' });
4. 注意事项与常见坑
-
避免内存泄漏:每次
subscribe的回调若为箭头函数或匿名函数,无法被unsubscribe精确移除 —— 应尽量传具名函数或保存引用 -
异步安全:默认同步通知;若需异步(如
Promise.then或queueMicrotask),需显式包装 -
错误隔离:单个回调报错不应中断其余回调执行,建议加
try/catch -
事件命名规范:建议用
kebab-case(如'data-loaded')或命名空间前缀(如'auth:login'),避免冲突
不复杂但容易忽略细节,按需选择是否支持通配符、异步队列、优先级等高级特性。基础版已覆盖大多数场景。


















