常规深拷贝工具无法正确处理getter访问器属性,因其跳过accessor、丢失this绑定或抛出错误;安全做法是提取描述符、确保依赖属性存在后用defineproperty逐个安装。

处理包含 getter 访问器属性的复杂对象深拷贝,不能依赖常规深拷贝工具——因为它们几乎都跳过 accessor(get/set)属性。关键在于:getter 本身不是数据,而是运行时逻辑;直接克隆描述符不等于克隆行为结果,更不保证 this 绑定正确。
为什么 standard 深拷贝会失效
主流方法在遇到 getter 时表现如下:
- JSON.stringify + JSON.parse:完全忽略 getter,连同 function、Symbol、undefined 一并丢弃
- Object.assign / 扩展运算符:只调用一次 getter 取值,把返回结果当普通 value 写入,不保留 getter 函数本身
- Lodash cloneDeep / structuredClone:默认跳过 get/set 描述符;structuredClone 遇到含 getter 的对象直接抛 DataCloneError
- Object.getOwnPropertyDescriptors + defineProperties:能复制 getter 函数,但若原 getter 依赖 this.xxx,而新对象缺少对应属性,就会报错或返回 undefined
手动重建 accessor 的安全做法
必须拆解描述符、分类处理、显式控制 this 上下文:
- 遍历
Object.getOwnPropertyDescriptors(source)获取全部属性元信息 - 对每个 key 判断:
if ('get' in desc || 'set' in desc),则保留 get/set 函数体 - 确保目标对象已存在 getter 所需的依赖属性(例如
get fullName() { return this.firstName + ' ' + this.lastName; }要求目标对象已有firstName和lastName) - 使用
Object.defineProperty(target, key, desc)安装,而非 defineProperties 批量操作(避免顺序依赖出错)
需要深拷贝值 + 保留 getter 的混合方案
适用于“部分字段需实时计算、其余字段需独立副本”的场景:
- 先用健壮深拷贝(如自定义 deepClone 或 structuredClone)生成纯数据副本
- 再单独提取原对象的 accessor 描述符,检查其内部是否仅读取本对象自有属性
- 若满足条件(无外部闭包、无 this 外部引用),将 getter 函数重新绑定到新对象上
- 示例:原对象有
get area() { return this.width * this.height; },深拷贝后 width/height 已是独立数值,可安全复用该 getter
Proxy 拦截实现按需克隆(适合超大嵌套对象)
不真正“延迟加载原始数据”,但可延迟克隆子树:
- 用 Proxy 包裹源对象,get 拦截器中检测访问路径是否命中未克隆的深层属性
- 首次访问
proxy.nested.items[0].detail时,才对nested层执行深拷贝并缓存 - 配合 WeakMap 缓存已克隆引用,避免重复处理同一源对象
- 注意:这仍是逻辑延迟,不是内存级懒加载;且要求源对象结构稳定、可枚举










