可通过继承原生eventtarget或手动模拟实现事件发布订阅;现代浏览器推荐直接extends eventtarget,旧环境可用map手动封装简易接口,并注意内存泄漏与遍历修改问题。

可以通过继承原生 EventTarget 或手动模拟其行为,为任意 JavaScript 对象添加事件发布订阅能力。现代浏览器中,EventTarget 不再仅限于 DOM 节点,可直接用于普通对象——这是最简洁、标准兼容的封装方式。
使用原生 EventTarget 直接继承(推荐)
只要目标环境支持(Chrome 64+、Firefox 60+、Safari 12.1+、Edge 79+),可直接 extends EventTarget:
- 定义一个类,显式继承
EventTarget,并在构造函数中调用super() - 该实例即拥有
addEventListener、dispatchEvent、removeEventListener方法 - 触发事件时需传入
Event实例(或自定义事件类),支持冒泡/捕获等特性(对非 DOM 对象无实际意义,但语法一致)
示例:
class MessageBus extends EventTarget {
send(data) {
this.dispatchEvent(new CustomEvent('message', { detail: data }));
}
}
const bus = new MessageBus();
bus.addEventListener('message', e => console.log('收到:', e.detail));
bus.send({ type: 'LOGIN', user: 'alice' }); // 输出: 收到: { type: 'LOGIN', user: 'alice' }
兼容旧环境:手动实现简易 EventTarget 接口
若需支持 IE 或老版本浏览器,可封装一个轻量级事件管理器,暴露与 EventTarget 相同的方法签名:
- 内部用 Map 存储事件名 → 回调数组映射
-
addEventListener(type, handler, options):将 handler 加入对应 type 的队列;options.once支持一次性监听 -
dispatchEvent(event):遍历执行该 type 下所有 handler,传入 event 对象(至少含type和detail) -
removeEventListener(type, handler):从队列中移除指定 handler
注意:不实现 stopPropagation 等 DOM 特有方法,保持语义清晰。
进阶:支持异步事件与上下文绑定
在业务逻辑中,常需控制事件响应时机或绑定 this:
- 在
dispatchEvent中使用queueMicrotask或setTimeout(..., 0)实现异步派发,避免同步阻塞 - 提供
once、off(批量移除)、emit(别名)等实用方法,提升开发体验 - 若 handler 是箭头函数或已绑定上下文,无需额外处理;否则可在
addEventListener内部自动.bind(this)(谨慎使用,避免内存泄漏)
注意事项与边界场景
使用封装后的事件系统时需留意:
- 原生
EventTarget实例无法被instanceof EventTarget检测(V8 引擎限制),但方法可用;如需类型判断,建议加自定义标志(如isEventTarget: true) - 自定义事件推荐用
CustomEvent,便于传递结构化数据(detail字段) - 避免在事件回调中修改正在遍历的监听器列表(如边 dispatch 边 remove),可能引发漏触发;可先收集待删 handler,遍历结束后统一清理
- 记得在对象销毁前调用
removeEventListener或提供destroy方法清空监听器,防止内存泄漏










