JavaScript拷贝含getter/setter的对象时,Object.assign等方法仅复制当前值或丢失存取器;深拷贝需用Object.getOwnPropertyDescriptors+defineProperty手动重建;但实际推荐避免保留存取器,优先用structuredClone获取数据快照。

JavaScript 中处理含存取器(getter/setter)属性的对象拷贝,是深浅拷贝中容易被忽略但很关键的一环。普通拷贝方法如 Object.assign()、扩展运算符或 JSON.parse(JSON.stringify()) 都会跳过 getter/setter,只复制其**当前计算出的值**(对 getter)或完全丢失(对 setter),无法保留属性的响应式行为或副作用逻辑。
浅拷贝对存取器属性的实际表现
浅拷贝操作(如 Object.assign({}, obj) 或 {...obj})只会遍历对象的可枚举自有属性,并读取其当前值——这意味着:
- getter 属性会被执行一次,结果值被静态写入新对象(不再是响应式计算)
- setter 属性不会被复制,新对象上对应 key 没有 setter,赋值将直接创建数据属性
- 不可枚举的存取器(如通过
Object.defineProperty显式设为enumerable: false)根本不会出现在拷贝结果中
示例:
const source = {
_value: 42,
get value() { console.log('getter triggered'); return this._value; },
set value(v) { console.log('setter triggered'); this._value = v; }
};
const shallow = { ...source };
console.log(shallow.value); // 输出 42,但不会打印 'getter triggered'
shallow.value = 99; // 不触发原 setter,而是新建一个数据属性 value = 99
深拷贝如何保留存取器逻辑?
标准深拷贝方案(包括 structuredClone() 和主流手写递归)**默认不支持存取器属性**——它们只处理数据属性,不复制访问器描述符。要真正保留 getter/setter,必须手动提取并重定义:
立即学习“Java免费学习笔记(深入)”;
- 使用
Object.getOwnPropertyDescriptors(obj)获取所有自有属性的完整描述符(含get/set字段) - 对每个描述符判断:若含
get或set,则在新对象中用Object.defineProperty重建该存取器 - 对纯数据属性,按常规方式深拷贝其值
基础实现示意:
function deepCloneWithAccessors(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj);
const cloned = Array.isArray(obj) ? [] : {};
map.set(obj, cloned);
// 获取所有自有属性描述符(含 getter/setter)
const descriptors = Object.getOwnPropertyDescriptors(obj);
for (const key in descriptors) {
const desc = descriptors[key];
if ('value' in desc) {
// 数据属性:深拷贝值
cloned[key] = deepCloneWithAccessors(desc.value, map);
} else if ('get' in desc || 'set' in desc) {
// 存取器属性:重建描述符(注意闭包捕获原对象需谨慎)
Object.defineProperty(cloned, key, {
get: desc.get ? () => desc.get.call(obj) : undefined,
set: desc.set ? (v) => desc.set.call(obj, v) : undefined,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
}
return cloned;
}
实际开发中的权衡建议
绝大多数业务场景下,**不建议强行保留存取器**,原因包括:
- getter/setter 往往依赖原始对象上下文(
this),重建后调用可能失效或产生副作用 - 存取器常用于计算属性、状态联动等,拷贝后失去与原对象关联反而违背设计意图
- 结构化克隆(
structuredClone)和 Lodash 的cloneDeep均明确不支持存取器,这是有意为之的取舍
更合理的做法是:
- 若需“行为一致”,应拷贝整个对象实例(如类实例),而非裸对象
- 若只需数据快照,接受 getter 被求值、setter 被丢弃的事实,用
structuredClone(支持 Date/Map/Set/循环引用)已足够健壮 - 对关键存取器逻辑,封装为独立函数或使用 Proxy 拦截,比依赖拷贝更可控


















