深拷贝默认不保留symbol属性,因json.stringify、for...in等忽略不可枚举的symbol键;structuredclone仅从chrome98+/node17+起支持symbol值,symbol键需手动用reflect.ownkeys和object.getownpropertysymbols处理。

JavaScript 中深拷贝默认不会保留 Symbol 属性,因为标准的深拷贝方法(如 JSON.parse(JSON.stringify())、结构化克隆、或多数第三方库的默认行为)只处理可枚举的字符串键,而 Symbol 是不可枚举且不被 JSON 支持的特殊类型。
Symbol 属性为何在深拷贝中容易丢失
Symbol 是唯一、不可枚举、不参与 JSON 序列化的原始类型。这意味着:
-
JSON.stringify()完全忽略 Symbol 键和值(包括作为对象属性名或属性值); -
for...in和Object.keys()遍历不到 Symbol 属性; - 原生结构化克隆(
structuredClone())从 Chrome 98+/Node.js 17+ 开始支持 Symbol,但仅限于 作为属性值(如{ sym: Symbol('a') }),不支持 Symbol 作为属性名(如{ [Symbol('k')]: 'v' }); - 大多数手动递归拷贝逻辑若未显式调用
Object.getOwnPropertySymbols(),就会遗漏 Symbol 键。
如何在深拷贝中保留 Symbol 属性(含 Symbol 键)
要完整保留 Symbol 键及其对应值,必须主动获取并复制所有自有 Symbol 属性。关键步骤如下:
- 使用
Object.getOwnPropertySymbols(obj)获取对象所有自有 Symbol 键; - 结合
Reflect.ownKeys(obj)(返回字符串键 + Symbol 键)确保全覆盖; - 对每个键,用
Object.getOwnPropertyDescriptor()提取描述符(含value、writable、enumerable、configurable),再用Object.defineProperty()复制到新对象; - 对 Symbol 值本身(如
obj[Symbol.for('x')] = Symbol('y')),直接赋值即可(Symbol 是原始值,拷贝即新引用,但注意:Symbol 没有“深浅”之分,它不可变)。
一个支持 Symbol 键的简易深拷贝示例
以下函数能正确复制字符串键、Symbol 键及嵌套结构(不含函数、undefined、循环引用等边界情况):
function deepCloneWithSymbols(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const cloned = Array.isArray(obj) ? [] : {};
// 复制所有自有属性(字符串键 + Symbol 键)
for (const key of Reflect.ownKeys(obj)) {
const desc = Object.getOwnPropertyDescriptor(obj, key);
const value = desc.value;
if (typeof value === 'object' && value !== null && !(value instanceof Date) && !(value instanceof RegExp)) {
desc.value = deepCloneWithSymbols(value);
}
Object.defineProperty(cloned, key, desc);
}
return cloned;
}
测试:
const sym = Symbol('id');
const original = { a: 1, [sym]: 'symbol-value' };
const copy = deepCloneWithSymbols(original);
console.log(copy[sym]); // 'symbol-value' ✅
console.log(Object.getOwnPropertySymbols(copy).length); // 1 ✅
实际开发中的建议
Symbol 属性通常用于内部状态或避免命名冲突,是否需要深拷贝取决于用途:
- 若 Symbol 属性是“元数据”(如
Symbol.toStringTag),多数场景无需深拷贝,保持原语义更安全; - 若业务逻辑依赖 Symbol 键(如插件系统用
Symbol('handler')存储回调),则必须显式处理; - 优先使用
structuredClone()(现代环境),它自动处理 Symbol 值,并在较新版本中已支持 Symbol 键(需确认运行时版本); - 使用 Lodash 的
cloneDeep()时注意:v4.17.21+ 已支持 Symbol 键,但需确保未启用自定义定制版(可能剔除了 Symbol 支持)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











