不能把订阅队列直接放在原型上,因为会导致所有实例共享同一队列,引发事件混乱、内存泄漏;正确做法是在构造函数中为每个实例创建独立的this._events,并将增删查、通知等逻辑封装在原型方法中复用。

在 JavaScript 中,原型对象本身不直接“存储”事件订阅队列;观察者模式的订阅队列通常属于实例(即每个被观察者对象自身),但可以通过原型方法统一管理这些队列——关键在于把队列放在实例上,把 增删查、通知等逻辑 提到原型上复用。
为什么不能把订阅队列直接放在原型上?
如果把事件队列(比如 this._events = {})写在原型上,所有实例会共享同一份对象。一旦一个实例添加了监听器,其他实例也会“看到”它,导致事件混乱、内存泄漏和不可预测的行为。
✅ 正确做法:在构造函数或初始化方法中为每个实例创建独立队列:
function EventEmitter() {
this._events = Object.create(null); // 每个实例独有
}
原型上定义通用的事件操作方法
把所有与订阅队列交互的逻辑封装在原型方法里,让实例能安全调用:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
-
on(event, handler):往this._events[event]数组里推入 handler -
off(event, handler):从对应数组中过滤掉指定 handler -
emit(event, ...args):遍历this._events[event]并依次调用 -
once(event, handler):内部用包装函数 +off实现一次执行
支持通配符或事件命名空间?靠实例队列结构灵活扩展
原型方法可以适配更复杂的队列结构。例如:
- 用
Map替代普通对象,支持任意类型事件键 - 把
_events设计为{ event: [{ fn, ctx, once }, ...] },支持上下文绑定和一次性监听 - 在
on方法里自动初始化this._events[event] = [],避免重复判断
继承场景下如何复用?保持实例隔离性
子类继承 EventEmitter 时,只要确保子类构造函数里调用了 EventEmitter.call(this),就能获得独立的 this._events。原型上的所有方法自然可继承,无需重写队列逻辑。
例如:
function Button() {
EventEmitter.call(this); // ✅ 继承并初始化自己的 _events
}
Button.prototype = Object.create(EventEmitter.prototype);
Button.prototype.click = function() {
this.emit('click');
};
这样每个 new Button() 都有自己的一套监听器,互不影响。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










