闭包在发布订阅模式中通过捕获外部变量(如事件名、回调数组)使监听函数持久访问私有状态。典型体现为:工厂函数中 events 作为闭包变量实现完全私有化,类中 this.events 依赖实例作用域;off 方法能精准移除回调正因共享同一闭包内的 handlers 数组;但需警惕内存泄漏,避免内联函数导致无法解绑。

闭包在发布订阅模式中保存监听函数,核心在于利用函数作用域“捕获”外部变量(比如事件名、回调列表),让监听函数即使在外部作用域消失后仍能访问和操作这些数据。
闭包保存监听函数的原理
发布订阅模式通常维护一个事件名到回调函数数组的映射(如 { click: [fn1, fn2], data: [fn3] })。当调用 on(eventName, callback) 时,如果直接把回调推入全局或模块级对象,看似没用闭包;但真正体现闭包价值的地方是:当你需要为每个事件单独封装管理逻辑(比如自动清理、带条件触发、绑定上下文)时,会创建一个内部函数,它引用了外层作用域的变量(如特定事件的 handlers 数组),这个内部函数就是闭包。
典型实现中的闭包体现
下面是一个轻量级 PubSub 示例,其中闭包用于隔离不同事件的监听器集合:
(注意:不是所有 on 方法都显式依赖闭包,但要支持动态事件、私有状态或防泄漏,闭包是自然选择)
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
- 每次调用
on(eventName, cb)时,内部会确保events[eventName]存在,并把cb推入对应数组——这个数组本身被闭包在模块/实例作用域内,外界无法直接修改 - 如果用类封装,
this.events是实例属性,方法通过this访问它;若用工厂函数返回对象,events就是闭包变量,更彻底地私有化 - 取消订阅
off(eventName, cb)能精准移除指定回调,正是因为它在同一个闭包作用域里读取并操作同一份handlers数组
避免内存泄漏的关键细节
闭包会延长变量生命周期,所以保存监听函数时要注意:
- 监听函数若引用了大型对象(如 DOM 元素、组件实例),而事件未及时解绑,会导致这些对象无法被 GC 回收
- 推荐在
off或组件销毁时主动清理:handlers = handlers.filter(fn => fn !== callback) - 若监听函数是箭头函数或内联函数(
on('data', () => {...})),则无法在off中准确匹配移除——因为每次都是新函数,此时闭包“保存”的其实是不可撤销的引用,应避免
用闭包封装带上下文的监听器
有时需要给监听函数绑定固定参数或 this 值,这时闭包很自然:
on('user:update', (data) => updateUser(data, userId)) —— 这个箭头函数就是一个闭包,捕获了外层的 userId 变量。它被存入 handlers 数组后,执行时仍能访问该值,无需依赖全局或额外传参。

















