Object.getOwnPropertyDescriptors()配合Object.defineProperties()可实现保留不可枚举属性的浅拷贝,因for...in、Object.keys()和JSON序列化均忽略不可枚举属性及访问器,而getOwnPropertyDescriptors能完整读取所有自有属性描述符。

直接用 Object.getOwnPropertyDescriptors() 获取所有自有属性(含不可枚举、getter/setter),再配合 Object.defineProperties() 创建新对象,就能实现保留不可枚举属性的浅拷贝。
为什么普通方法无法拷贝不可枚举属性
for...in 和 Object.keys() 都只遍历可枚举属性;JSON.parse(JSON.stringify()) 会丢弃函数、undefined、Symbol 键、不可枚举属性和 getter/setter。而 Object.getOwnPropertyDescriptors() 返回对象所有自有属性的完整描述符(包括 enumerable: false、get、set、writable、configurable),是真正“无损读取”的关键。
核心实现步骤
- 调用
Object.getOwnPropertyDescriptors(obj)获取源对象全部自有属性描述符对象 - 用
Object.defineProperties({}, descriptors)将这些描述符批量定义到一个空对象上 - 返回该新对象——它与原对象在属性存在性、枚举性、访问器行为、写入/配置能力上完全一致(浅层)
基础代码示例
注意:仅适用于普通对象,不处理原型链、Symbol 键、循环引用
function shallowCopyWithNonEnumerable(obj) {
if (obj == null || typeof obj !== 'object') return obj;
return Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
}
// 示例
const source = {};
Object.defineProperty(source, 'hidden', {
value: 42,
enumerable: false,
writable: true
});
Object.defineProperty(source, 'count', {
get() { return this._count || 0; },
set(v) { this._count = v; },
enumerable: false
});
const copy = shallowCopyWithNonEnumerable(source);
console.log(copy.hidden); // 42
console.log(Object.getOwnPropertyDescriptor(copy, 'hidden').enumerable); // false
console.log('hidden' in copy); // true
console.log(Object.keys(copy)); // [] (不可枚举,所以 keys 为空)
补充说明与注意事项
- 该方式拷贝的是属性定义本身,不是值的深拷贝——若属性值是对象,拷贝后仍指向同一引用
- 不会继承原型上的属性,只处理对象自身的属性(符合“自有”语义)
- Symbol 类型的属性也会被包含在
getOwnPropertyDescriptors结果中(ES2017+) - 若需兼容旧环境,可先用
Object.getOwnPropertyNames()+Object.getOwnPropertySymbols()手动合并描述符

















