不能只靠 proxy 的 get/set 实现可靠 ui 绑定,必须配合依赖收集、副作用调度及对 map/set/array 等原生结构的函数劫持,否则 map.get('key')、arr.push() 等操作无法触发响应式更新。

直接说结论:不能只靠 Proxy 的 get/set 就实现可靠 UI 绑定,必须配合依赖收集、副作用调度、以及对 Map/Set/Array 等原生结构的函数劫持——否则读取 map.get('key')、调用 arr.push() 时根本收不到通知。
为什么单纯 Proxy.get/set 无法覆盖真实绑定场景
Vue2 用 Object.defineProperty 失败的关键教训就在这里:它只能拦截「已存在属性」的读写。而现代 JS 中大量操作不走属性访问路径:
-
map.get('a')触发的是gettrap 中的key === 'get',不是'a'—— 你收集的依赖对象是map上的get方法,而非键'a' -
arr.push(1)完全不触发任何get/set,因为它是数组原型方法调用,不是属性赋值 -
for...of遍历Set时,Proxy 默认不拦截迭代器行为,[[Iterator]]不会被 trap
所以你看到 Vue3 源码里 reactive 对 Map 返回的不是裸 Proxy,而是包裹了 createInstrumentationGetter 的 handler,里面重写了 get、has、add、delete 等所有可枚举方法。
必须劫持的原生方法列表(按数据类型分)
不同构造器需要重写的 API 完全不同,漏掉任意一个都会导致响应式断裂:
-
Map:必须重写
get、set、has、delete、clear、forEach;keys()/values()/entries()返回的迭代器也得包装成响应式 -
Set:必须重写
add、delete、has、clear、forEach;values()迭代器要劫持 -
Array:除
push、pop、shift、unshift、splice、sort、reverse外,连length赋值都得单独处理(因为会触发索引重排)
这些方法不能简单地 Reflect.apply(target[key], target, args) 后返回——你得在调用前调用 track(target, key, 'iterate'),调用后调用 trigger(target, key, 'add' | 'delete' | 'set'),否则 UI 不知道该不该更新。
安装并运行 Aperture(Lightning Labs 的 L402 闪电反向代理),用于创建 L402 付费墙、配置付费 API 端点、为其他代理托管付费内容或测试 L402 认证流程。
副作用函数如何与 Proxy 关联起来
核心是全局变量 activeEffect + 闭包捕获机制,不是靠 Proxy 自身存储:
- 每次执行用户写的响应式函数(比如
() => state.count + state.name),先将它赋给activeEffect,再执行 -
gettrap 内部调用track(target, key),该函数把当前activeEffect存进target的依赖图(例如target.__deps__.get(key).add(activeEffect)) -
set或add等 trap 执行后调用trigger(target, key),遍历对应 key 的所有 effect 并加入执行队列
注意:trigger 不能同步执行 effect,否则嵌套修改会重复触发或栈溢出;Vue3 用 queueJob 做微任务调度,你若手写至少得用 Promise.resolve().then(() => effects.forEach(run))。
容易被忽略的边界:Proxy 无法代理非对象类型
Proxy 只能代理对象(包括 Array、Map、Set、Function),但实际业务中常有 ref(123)、ref('hello') 这类基本类型响应式需求:
- 必须用
Object包一层,比如{ value: 123 },再对其value属性做Proxy劫持 - 不能对原始字符串/数字直接
new Proxy('str', ...),会报TypeError: Cannot create proxy with a non-object as target or handler - 所以
ref和reactive是互补方案:前者解决基本类型,后者解决对象结构;混用时要注意ref解包需用.value,而reactive是自动解包的
真正难的不是写通一个 get,而是让 state.list.push(item) 和 state.map.set('k', v) 在 UI 层看起来完全一致——这要求你在 handler 里识别出调用上下文,并把所有可能触发视图变化的操作都归一化到同一套依赖追踪路径上。










