JavaScript响应式核心是闭包封装状态+Proxy拦截操作:闭包隔离data/effects并自然捕获作用域,Proxy通过get/set实现依赖收集与触发,递归代理保障深层响应,WeakMap避免内存泄漏。

JavaScript 中函数的动态能力,关键不在“运行时改代码”,而在于让行为随数据状态自然响应。闭包与 Proxy 的结合,正是实现这种响应式动态性的经典范式:闭包提供私有、稳定的状态容器,Proxy 提供可拦截、可扩展的行为入口,二者配合,让对象具备“感知变化”和“自动反应”的能力。
闭包负责封装与隔离
它把原始数据、依赖列表(如 effects 或 observers)、更新函数等变量锁在函数作用域内,外部无法直接读写,也避免了全局污染。每次调用 reactive() 生成代理时,都会创建一个独立闭包,确保不同对象的状态互不干扰。
- 典型结构是
const reactive = (obj) => { const data = {...obj}; const effects = []; return new Proxy(data, handler); } - handler 中的
get和set能直接访问effects和data,不是靠参数传递,而是靠词法作用域的自然捕获 - WeakMap 缓存代理实例也定义在此闭包中,既安全又自动跟随原始对象生命周期回收
Proxy 负责拦截与调度
它不改变数据本身,而是接管所有对目标对象的操作入口。通过 get 拦截读取,完成依赖收集;通过 set 拦截赋值,触发已注册的副作用。整个过程对使用者透明——你照常读写属性,变化却已被捕获并响应。
-
get中调用track()记录当前正在执行的 effect(需配合 activeEffect 标记) -
set中先用Reflect.set()更新值,再调用trigger()遍历并执行所有关联 effect - 对数组索引赋值、
length修改、新增属性等边界操作,也要覆盖,否则监听会失效
嵌套响应需要递归代理
Proxy 默认只代理第一层,深层属性修改不会触发顶层通知。解决方案是在 get 拦截器中判断返回值是否为对象,若是则递归调用 reactive() 并返回子代理——这个子代理共享同一套闭包内的 effects 和 notify 逻辑,保证 obj.a.b.c 的变更仍走原始 obj 的观察者队列。
立即学习“Java免费学习笔记(深入)”;
- 避免每个层级新建 observers 数组,防止监听错位或重复执行
- 循环引用场景下,WeakMap + 闭包能正确维持引用关系,不造成内存泄漏
- 深层代理不是“复制”,而是“连接”,整个嵌套结构共用同一通知机制
常见陷阱与规避要点
这种组合威力大,但细节决定成败。几个高频出错点值得特别注意:
-
effects = []这类重赋值操作会切断闭包引用,应始终用effects.push()/effects.pop()维持原数组引用 - effect 内部直接读写同一属性,可能引发无限循环,需加执行标记(如
isSetting)控制时机 - 不用 class 封装 handler 是有道理的:闭包更轻量,无 this 绑定开销,状态流转更可控


















