闭包实现观察者模式通过立即执行函数封装私有 listeners 对象,对外暴露 on、emit、off 和 once 方法;once 内部用包装函数自动解绑,emit 时浅拷贝数组防遍历异常,支持单例导出。

用闭包实现简易观察者模式,核心是利用闭包保存私有的事件监听器列表,对外只暴露 on(订阅)、emit(触发)和可选的 off(取消订阅)方法,避免外部直接操作监听器数组。
基础结构:闭包封装事件池
通过立即执行函数返回一个对象,内部维护一个 listeners 对象(以事件名为键),所有操作都通过返回的方法进行,保证数据私有性:
const createEventBus = () => {
const listeners = {}; // 私有,外部无法访问
return {
on(event, callback) {
if (!listeners[event]) listeners[event] = [];
listeners[event].push(callback);
},
emit(event, ...args) {
if (listeners[event]) {
listeners[event].forEach(cb => cb(...args));
}
},
off(event, callback) {
if (listeners[event]) {
listeners[event] = listeners[event].filter(cb => cb !== callback);
}
}
};
};
// 使用示例
const bus = createEventBus();
bus.on('dataReady', (data) => console.log('收到:', data));
bus.emit('dataReady', { id: 1 }); // 输出:收到:{ id: 1 }
支持一次性监听(once)
在闭包内扩展 once 方法,内部自动在执行后调用 off 移除自己:
- 注册时把包装后的回调存入数组,执行一次就删掉
- 注意不能直接用箭头函数做包装,否则
off无法比对原函数引用 - 推荐用闭包绑定原始回调和事件名,确保可移除
const createEventBus = () => {
const listeners = {};
const off = (event, callback) => {
if (listeners[event]) {
listeners[event] = listeners[event].filter(cb => cb !== callback);
}
};
return {
on(event, callback) {
if (!listeners[event]) listeners[event] = [];
listeners[event].push(callback);
},
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
off(event, wrapper); // 执行完立刻解绑
};
this.on(event, wrapper);
},
emit(event, ...args) {
if (listeners[event]) {
listeners[event].forEach(cb => cb(...args));
}
},
off(event, callback) {
off(event, callback);
}
};
};
增强健壮性的小细节
实际使用中建议补充几处处理,让闭包版更可靠:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
立即学习“Java免费学习笔记(深入)”;
- 给
on和once增加对非函数回调的校验,避免静默失败 -
emit时遍历前先浅拷贝监听器数组,防止回调内部调用off导致遍历异常 - 可选添加
clear(event)清空某事件所有监听器,或list(event)查看当前监听数
// emit 改进版(防并发修改)
emit(event, ...args) {
const cbs = listeners[event]?.slice() || [];
cbs.forEach(cb => cb(...args));
}
和 class 版对比的优势与局限
闭包实现轻量、无实例状态污染,适合工具级总线(如跨模块通信);但每次调用 createEventBus() 都新建一套监听器,不共享。若需全局唯一总线,可导出单例:
// 单例写法(ES Module 环境) export const EventBus = createEventBus(); // 模块加载时只初始化一次
它不依赖 this 或原型链,没有继承/实例化开销,函数式风格清晰,适合嵌入配置对象或作为依赖注入的事件服务。

















