Proxy只能代理对象,不能直接代理原始类型;需通过包装对象(如{value: x})、封装集合方法或统一store等方式实现对各类数据的可观测性。

Proxy 可以监控对象属性的读取、赋值、删除等操作,但它本身只适用于对象(包括数组、函数、Map、Set 等——只要可被当作目标对象传入 new Proxy(target, handler)),不能直接代理原始类型(如 number、string、boolean、null、undefined、symbol)。不过,我们可以通过包装、转换或约定方式,实现对各类数据访问行为的统一可观测性。
1. 代理普通对象与数组:基础用法
这是 Proxy 最自然的使用场景。只需将目标对象/数组作为第一个参数传入,并在 handler 中定义 trap(拦截方法):
- get:拦截属性读取,可用于记录访问路径、触发依赖收集(如响应式系统)
- set:拦截赋值,可校验、转换、通知变更
- has、deleteProperty、ownKeys 等可分别监控 in 操作、delete、Object.keys() 等行为
示例:
const obj = { count: 0 };const proxied = new Proxy(obj, {
get(target, key) { console.log('读取', key); return target[key]; },
set(target, key, value) { console.log('设置', key, value); target[key] = value; return true; }
});
2. 代理数组:注意 length 和索引赋值的特殊性
数组是对象,Proxy 可完整代理。但需注意:arr[0] = x、arr.push()、arr.length = 0 都会触发 set;而 arr.pop()、arr.map() 等方法内部调用的是原型方法,不会自动触发 set/get —— 若需完全监控,应同时代理原型(不推荐)或封装数组方法(更实用)。
立即学习“Java免费学习笔记(深入)”;
- 推荐做法:用 Proxy 封装数组实例,并重写常用方法(如 push、pop、splice),在其中手动触发变更通知
- length 属性的修改也会进入
set,可在此做边界检查或同步更新内部状态
3. 代理 Map / Set / WeakMap / WeakSet:需包装而非直接代理
这些集合类型本身不可被 Proxy 直接“深层代理”,因为它们的键值操作(如 map.set(k, v))不走属性访问,而是调用方法。正确做法是创建一个包装器类:
- 对外暴露一个 Proxy 对象,其 get/set 拦截转为调用内部 Map 的
get/set方法,并插入日志或通知逻辑 - 例如:拦截
proxy.key→ 转为innerMap.get('key');拦截proxy.key = val→ 转为innerMap.set('key', val) - 也可代理整个包装实例的方法调用(用
apply拦截构造函数或方法),但通常按属性映射更直观
4. 监控原始类型:必须借助包装对象
JavaScript 中无法对 let x = 42 这样的原始值做 Proxy。可行方案只有:
-
用对象包裹:如
{ value: 42 },再代理该对象 → 访问ref.value即被监控 -
约定访问方式:所有原始值统一通过 getter/setter 函数操作,例如
getValue('count')/setValue('count', 5),底层用 Proxy 管理一个中心 store 对象 - 现代实践参考:Vue 3 的
ref()返回的就是一个带.value的 proxy 包装对象;SolidJS 的createStore或信号(Signal)也基于类似思路
关键点:原始类型不可代理,但“持有原始类型的对象”可以;监控的本质是控制访问入口,而非改造值本身。


















