闭包的核心作用是为每个响应式属性的 getter 提供独立、持久、私有的订阅集合(如 Set),实现“谁读了我,我就记住谁”的依赖收集;其原理是在 defineProperty 或 Proxy 的 get trap 中,通过闭包捕获并维护该属性专属的 deps 集合,避免全局污染或重复创建。

闭包在响应式数据依赖收集代理中,核心作用是**让每个响应式属性的 getter 拥有独立、持久、私有的订阅集合(如 Set)**,从而实现“谁读了我,我就记住谁”——这正是依赖收集的关键。
闭包保存订阅集合的原理
在 Proxy 的 get 拌手(trap)中,每次访问属性时,需要把当前正在执行的副作用函数(如 render)添加到该属性对应的依赖集合里。这个集合不能挂到全局或共享对象上,否则会交叉污染;也不能每次 new 一个,否则旧依赖就丢了。闭包提供了一个天然的私有作用域:
- 在定义响应式对象时,为每个属性单独创建一个闭包环境
- 该闭包内声明一个 let deps = new Set(),对外不可直接访问
- get trap 在其中调用 deps.add(activeEffect),add 的是当前活跃的副作用
- set trap 触发更新时,遍历并执行 deps.forEach(fn => fn())
典型实现结构(简化版)
以 Vue 3 的 reactive + effect 逻辑为参考,关键不是 Proxy 本身,而是闭包如何配合:
function createDep() {
const deps = new Set()
return {
add: (effect) => deps.add(effect),
delete: (effect) => deps.delete(effect),
notify: () => deps.forEach(fn => fn())
}
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
// 从闭包中获取该 key 对应的 dep 实例
const dep = getDep(target, key) // 例如用 WeakMap 缓存 {target → Map<key dep>}
if (activeEffect) {
dep.add(activeEffect) // 闭包内的 deps 被安全捕获
}
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
const dep = getDep(target, key)
dep.notify() // 执行所有订阅者
return result
}
})
}</key>
这里 createDep 返回的对象内部始终持有对自身 deps 的引用,而 Proxy 的 get/set 每次都复用这个闭包绑定的 dep 实例——这就是闭包保存状态的本质。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
为什么不用普通对象属性?
如果直接把 deps 放在 target[key] 上(比如 obj._deps = new Set())会有问题:
- 破坏原始数据结构,影响 JSON 序列化、跨框架互操作
- 用户可能意外覆盖或删除 _deps
- 无法处理相同 key 在不同 proxy 实例间的隔离(比如两个 reactive({a:1}) 应该各自维护 a 的依赖)
而闭包 + WeakMap(target → Map
实际开发中要注意的点
闭包本身不难,但结合响应式系统容易踩坑:
- 避免闭包过大:不要在闭包里捕获整个组件实例或大型对象,防止内存泄漏
- WeakMap 是关键桥梁:它让 target 和 dep 映射关系可回收,否则 proxy 一直持有着 target 引用
- dep 必须唯一且稳定:同一个 key 在同一 target 上,每次 get 都要返回同一个 dep 实例,不能每次都 new
- effect 清理时要从 dep 中 remove:否则已卸载组件的 render 还会被调用
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










