object.getownpropertydescriptors 配合 object.defineproperties 可实现保真浅拷贝,完整保留 getter/setter、不可枚举属性及 writable/enumerable/configurable 状态,而 object.assign 和展开运算符仅复制属性值、丢失元信息。

Object.getOwnPropertyDescriptors 本身不直接实现浅拷贝,但它能精确捕获对象所有自有属性的完整描述(包括 value、writable、enumerable、configurable,甚至 get/set 访问器),配合 Object.defineProperties,可实现比 Object.assign 或展开运算符更“保真”的浅拷贝——尤其对访问器属性、不可枚举属性、不可写属性等关键元信息做到零丢失。
为什么普通浅拷贝会“丢东西”?
常见方法如 {...obj} 或 Object.assign({}, obj) 只复制属性值,忽略:
- getter/setter(直接调用 getter 取值,丢失访问器逻辑)
- 不可枚举属性(如
Object.defineProperty(obj, 'hidden', { value: 42, enumerable: false })) - 属性的 writable/configurable/enumerable 状态(新属性默认全为
true)
用 getOwnPropertyDescriptors + defineProperties 实现保真浅拷贝
核心思路:先获取源对象所有自有属性的完整 descriptor 对象,再用这些 descriptor 在新对象上逐个定义属性。
示例代码:
function shallowCopyWithDescriptors(obj) {
if (obj == null || typeof obj !== 'object') return obj;
const descriptors = Object.getOwnPropertyDescriptors(obj);
return Object.defineProperties({}, descriptors);
}
// 测试:含访问器、不可枚举、只读属性
const source = {};
Object.defineProperty(source, 'count', {
get() { return this._count || 0; },
set(v) { this._count = Math.max(0, v); },
enumerable: true,
configurable: true
});
Object.defineProperty(source, 'version', {
value: '1.0',
writable: false,
enumerable: false,
configurable: false
});
const copy = shallowCopyWithDescriptors(source);
console.log(copy.count); // 0(getter 正常工作)
console.log(Object.getOwnPropertyDescriptor(copy, 'version').writable); // false(状态保留)
console.log(Object.keys(copy)); // ['count'](version 不可枚举,不出现在 keys 中)
注意边界与限制
该方法仍是浅拷贝,不会递归处理嵌套对象或数组:
- 如果某个属性值是对象(如
{ nested: { x: 1 } }),拷贝后copy.nested === source.nested(引用未变) - 无法处理 Symbol 属性?可以——
getOwnPropertyDescriptors默认包含 Symbol 键(只要它是自有属性) - 不能拷贝原型链上的属性(符合“自有属性”语义,这是优点而非缺陷)
- 不处理函数内部的闭包、this 绑定等,仅关注对象自身的属性结构
什么时候该用它?
适合需要严格复刻对象“表面契约”的场景:
- 构建代理(Proxy)前的原始对象快照
- 测试中模拟带访问器/只读约束的配置对象
- 序列化前预处理:确保不可枚举、不可写等元信息在 JSON.stringify 前已按需调整
- 封装类库时,安全地暴露对象副本而不泄漏原始控制权











