如何用闭包实现一个简单的发布订阅者模式
闭包通过在工厂函数内声明handlers数组并返回操作它的函数,确保订阅者列表私有且持久;事件名作键、回调作引用值,解绑需严格比对函数实例;emit中用slice()复制数组避免遍历时被修改。闭包怎么保存订阅者列表闭包的核心作用是让内部函数持续访问外部函数的变量,而不被垃圾回收。发布订阅模式里,订阅者列表 handlers 必须私有且持久——不能暴露给外部直接修改,也不能在每次调用时重置。常见错误是把 handlers 声明在全局或模块顶层,导致多个实例共享同一组回调;或者写成普通对象方法,this 绑定错、状态丢失。必须在工厂函数(如 createEventBus)内部声明 handlers 数组所有操作(on、emit、off)都通过返回的闭包函数访问它避免使用 class 或 this,否则闭包意义被削弱,容易意外暴露状态function createEventBus() { const handlers = {}; return { on(event, fn) { /* ... */ }, emit(event, data) { /* ... */ }, off(event, fn) { /* ... */ } };}如何正确处理事件名和回调的绑定与解绑事件名是字符串键,回调是引用值,解绑(off)失败最常见原因是:用匿名函数订阅后试图用另一个匿名函数解绑,或没比对函数引用而是比对内容。闭包模式下,handlers[event] 存的是函数引用数组,off 时必须用 === 判断是否为同一函数实例。禁止在 on 里传入内联箭头函数:bus.on('click', () => {...}) → 后续无法 offoff 时要遍历 handlers[event],用 findIndex + === 匹配,再用 splice支持通配符(如 'user.*')需额外解析逻辑,不在基础闭包范围内,容易引入正则或字符串匹配开销const handler = (data) => console.log(data);bus.on('login', handler);bus.off('login', handler); // ? 可行bus.off('login', (d) => console.log(d)); // ? 不会生效为什么 emit 里要用 slice() 遍历回调列表执行 emit 时,回调函数内部可能调用 on 或 off,直接遍历原数组会导致索引错乱、漏调或重复调用——这是闭包封装下最容易被忽略的并发陷阱。 Cleanup.pictures 智能移除图片中的物体、文本、污迹、人物或任何不想要的东西
