Proxy 本身不触发拷贝,需通过 set 拦截赋值并手动实现浅/深拷贝;须区分原始值与引用类型,避免误拷贝不可序列化值;需维护源与副本关联,并递归代理嵌套对象以防漏控。

Proxy 本身不能直接触发拷贝操作,它只是 JavaScript 中用于拦截和自定义对象基本操作(如读取、赋值、删除等)的元编程工具。要实现“自动拷贝”,关键在于:利用 Proxy 拦截赋值行为,在赋值时同步生成副本,并确保副本与原数据保持预期的一致性(浅拷贝 or 深拷贝)。
拦截 set 操作,触发拷贝逻辑
Proxy 的 set trap 是核心入口。当对代理对象进行赋值(如 obj.key = value)时,你可以在此拦截,决定是否、何时、如何拷贝目标值。
- 判断是否需要拷贝:比如只对特定 key(如
data)、特定类型(如 plain object 或 array)的值做深拷贝 - 执行拷贝:调用
structuredClone()(现代环境)、JSON.parse(JSON.stringify())(简单场景)、或使用 Lodash 的cloneDeep - 将拷贝结果存入备份对象或另一个属性,例如
obj._backup.data = structuredClone(value)
区分原始值与引用值,避免误拷贝
不是所有赋值都需要拷贝。数字、字符串、布尔值等原始类型无需深拷贝;而对象、数组、Date、Map 等引用类型才需处理。
- 用
typeof value === 'object' && value !== null初筛 - 进一步用
Array.isArray(value)、value.constructor === Object或isPlainObject()排除函数、正则、DOM 节点等不适合拷贝的类型 - 对不可序列化的值(如函数、undefined、Symbol),可跳过拷贝或抛出提示
维护拷贝与源的关联关系
自动拷贝不是孤立动作,常用于状态快照、撤销/重做、或响应式更新。需明确“谁拷谁”:
- 方式一:为每个被代理对象配一个独立的备份对象,Proxy 在 set 时同步更新备份
- 方式二:代理整个 state 对象,当某个子属性变更时,仅拷贝该路径对应值(配合
Reflect.set()和路径追踪) - 方式三:结合 WeakMap 缓存原始对象与其副本映射,避免重复拷贝同一对象
注意陷阱:Proxy 不会自动递归代理嵌套对象
默认情况下,Proxy 只代理最外层对象。若你赋值的是一个新对象(如 obj.nested = { a: 1 }),这个新对象不会自动被 Proxy 包裹,后续对其修改也不会触发你的拷贝逻辑。
- 解决方案:在
set中检测新赋值是否为对象,如果是,递归 wrap 它(即返回一个新的 Proxy) - 但要注意循环引用,可用 WeakMap 记录已代理对象,避免无限递归
- 也可选择惰性代理——只在首次访问嵌套属性时才创建 Proxy,减少开销


















