浅拷贝不复制不可枚举属性是因为object.assign()、扩展运算符等依赖枚举机制(如object.keys()、for...in),而它们只遍历可枚举的自有属性;要保留不可枚举属性,须用object.getownpropertydescriptors()配合object.create()手动复制。

JavaScript 中的浅拷贝方法(如 Object.assign()、扩展运算符 {...obj}、Object.create(obj) 等)默认**只复制可枚举的自有属性**,而**不可枚举属性(包括 `[[Enumerable]]: false` 的属性)不会被复制**。这是由 JavaScript 的属性遍历机制决定的——这些方法底层依赖 `for...in` 或 `Object.keys()`,二者均忽略不可枚举属性。
为什么浅拷贝不复制不可枚举属性?
因为标准浅拷贝工具基于“属性枚举”行为:
-
Object.keys()、for...in、Object.assign()和展开语法都只访问 可枚举的自有属性 - 不可枚举属性(例如通过
Object.defineProperty()设置enumerable: false的属性)不在遍历范围内 - 即使该属性是自有属性(不是原型链上的),只要
enumerable为false,就不会被拷贝
如何让浅拷贝包含不可枚举属性?
必须绕过枚举限制,手动获取所有自有属性(无论是否可枚举),再逐个复制。核心是使用 Object.getOwnPropertyNames() 或 Object.getOwnPropertyDescriptors():
-
Object.getOwnPropertyNames(obj):返回所有自有属性名(含不可枚举的字符串键) -
Object.getOwnPropertySymbols(obj):获取所有自有 Symbol 属性(也含不可枚举的) -
Object.getOwnPropertyDescriptors(obj):获取所有自有属性的完整描述符(含enumerable、writable、configurable、value或get/set),最稳妥
示例(保留不可枚举属性的浅拷贝):
function shallowCopyWithNonEnumerable(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const descriptors = Object.getOwnPropertyDescriptors(obj);
const symbols = Object.getOwnPropertySymbols(obj);
// 合并字符串键和 Symbol 键的描述符
symbols.forEach(sym => {
descriptors[sym] = Object.getOwnPropertyDescriptor(obj, sym);
});
return Object.create(
Object.getPrototypeOf(obj),
descriptors
);
}
// 使用示例
const source = {};
Object.defineProperty(source, 'hidden', {
value: 'secret',
enumerable: false,
writable: true,
configurable: true
});
source.normal = 'ok';
const copy = shallowCopyWithNonEnumerable(source);
console.log(copy.hidden); // 'secret' ✅
console.log(Object.propertyIsEnumerable.call(copy, 'hidden')); // false ✅(保持不可枚举)
注意原型链与不可枚举属性的关系
不可枚举属性如果定义在原型上(比如 Array.prototype.map),它本来就不属于对象自有属性**,因此任何浅拷贝都不会复制它——这和“可枚举与否”无关,而是“自有 vs 继承”的问题。
- 浅拷贝只处理目标对象自身的属性(own properties)
- 原型上的不可枚举属性需通过
Object.getPrototypeOf()显式访问,不能靠拷贝继承 - 若需复制原型链结构,应使用
Object.setPrototypeOf()或构造新对象时指定原型
常见误区提醒
以下方式仍然无法复制不可枚举属性:
-
JSON.parse(JSON.stringify(obj)):跳过函数、undefined、Symbol、不可枚举属性、循环引用 -
structuredClone()(ES2022):虽支持 Symbol 和部分不可枚举属性,但仍不保证保留enumerable: false状态(实际中常转为可枚举) -
Object.assign({}, obj):仅拷贝可枚举自有属性
真正可靠的方式,始终是用 Object.getOwnPropertyDescriptors() + Object.create() 组合,显式控制每个属性的特性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











