Proxy实现响应式的核心是拦截读写、建立依赖、触发更新,通过get/set等trap覆盖Object.defineProperty盲区,结合activeEffect依赖收集与执行机制,并递归代理嵌套对象与数组,同时规避解构、遍历、序列化等陷阱。

用 Proxy 实现对象的响应式数据绑定,核心是拦截读写操作、建立依赖关系、并在数据变化时触发更新。它不是简单地“监听属性”,而是通过一套协作机制——代理层 + 依赖收集 + 副作用执行——让数据和视图自动联动。
Proxy 拦截读写,覆盖 Object.defineProperty 的盲区
Object.defineProperty 只能劫持已存在的属性,对以下操作无能为力:
- 动态添加或删除属性(
obj.newKey = 1或delete obj.key) - 数组索引赋值(
arr[0] = 'x')、push/pop等方法调用 - 访问
length、遍历(for...in、Object.keys())等不触发 get 的操作
Proxy 通过多个 trap(陷阱)统一覆盖:
-
get:读取属性时收集依赖 -
set:赋值时触发更新,并返回Reflect.set()保证原语义 -
deleteProperty:支持响应式删除 -
ownKeys和has:让Object.keys()、in操作也能被追踪 -
getOwnPropertyDescriptor等:配合 Reflect 保持行为一致
依赖必须“知道谁在读”,靠 activeEffect 关联
响应式不是 Proxy 自身的能力,而是靠外部逻辑把「读操作」和「更新函数」连起来:
- 定义一个全局变量
let activeEffect = null,表示当前正在执行的副作用函数(如渲染函数) - 在
gettrap 中,若activeEffect存在,就将它存入该对象+属性对应的依赖集合(例如用WeakMap<object map set>>></object>) - 在
settrap 中,查出该属性的所有 effect,依次执行
这样,effect(() => console.log(obj.count)) 执行时会自动把自身记进 obj.count 的依赖里;后续 obj.count++ 就能精准触发它。
嵌套对象和数组要递归代理,但需缓存防死循环
用户访问 state.user.profile.name 时,中间的 user 和 profile 也得是响应式的:
-
get返回值如果是对象或数组,不能直接返回原值,而要调用reactive()包装 - 用
Map缓存已代理的对象:if (proxyMap.has(raw)) return proxyMap.get(raw) - 对数组特殊处理:检测
Array.isArray(value),返回新代理数组,避免原生数组赋值绕过 set - 跳过原始值(
null、undefined、字符串、数字等),直接返回,不代理
避开常见坑:解构、遍历、序列化
这些操作不走 get,因此不会收集依赖,导致响应式失效:
-
解构赋值:
const { count } = state→count是普通变量,脱离响应链。应改用const { count } = toRefs(state),它返回每个属性的 ref 对象 -
遍历操作:
for...in、Object.keys()触发ownKeystrap,需在其中调用track(target, 'keys')收集依赖 -
JSON.stringify() 会无限递归代理对象(因为
toString等内部操作反复读属性)。解决方式:对响应式对象加标记(如__v_isReactive),序列化前先toRaw()转回原始对象


















