深拷贝无法原样保留函数和Symbol因其不可序列化:函数依赖运行时状态,Symbol不可枚举;应按场景跳过、引用、白名单或解耦设计。

深拷贝过程中,函数和 Symbol 属性无法被标准序列化机制(如 JSON 或 structuredClone)原样保留,是因为它们本质上不属于“可转移数据”:函数携带执行上下文与闭包,Symbol 是不可枚举且原始值类型的唯一标识。处理它们的关键不是强行“复制”,而是根据场景选择合理策略——跳过、保留引用、显式提取或解耦设计。
函数属性:默认跳过最安全,有需才干预
几乎所有主流深拷贝方案(JSON.parse(JSON.stringify())、structuredClone()、Lodash 的 cloneDeep)都主动忽略函数。这不是缺陷,而是有意为之:
- 函数不是纯数据,它依赖闭包变量、
this绑定、原型链等运行时状态,无法可靠重建 - 尝试用
new Function(...)字符串化重建会丢失闭包、不支持箭头函数、存在 XSS 风险,生产环境禁用 - 多数业务中,函数本就该被共享而非复制;深拷贝后仍调用同一份逻辑,更符合预期
若确实需要保留某些函数(比如配置中的回调名),推荐白名单方式,在递归拷贝逻辑中显式判断并赋值:
if (key === 'onSubmit' || key === 'transform') cloned[key] = source[key];Symbol 属性:必须用 Object.getOwnPropertySymbols() 显式提取
Symbol 键不会出现在 for...in、Object.keys() 或 JSON.stringify() 中,因为它们默认不可枚举。遗漏 Symbol 就等于丢掉关键元信息(如私有状态标识、插件扩展点)。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 获取所有 Symbol 键:用
Object.getOwnPropertySymbols(source)得到数组 - 逐个拷贝值:对每个
sym,判断source[sym]类型,基本类型直赋,对象/数组则递归深拷贝 - 写入目标对象:直接
target[sym] = clonedValue即可——Symbol 本身是原始值,复用原 Symbol 实例完全安全
更统一的做法是改用 Reflect.ownKeys(source),它同时返回字符串键和 Symbol 键,便于合并遍历,避免漏掉非枚举的字符串属性。
循环引用 + 多类型兼容:手写深拷贝需兼顾三要素
要真正可控地处理函数、Symbol 及其他边界类型,手写递归深拷贝仍是必要手段,但必须包含三个核心机制:
- WeakMap 缓存已处理对象:检测并截断循环引用,防止栈溢出
-
精确类型识别:不用
typeof判 Date/RegExp/Map/Set,改用Object.prototype.toString.call()或构造器比对 -
双通道键遍历:先处理
Object.keys()(字符串键),再处理Object.getOwnPropertySymbols()(Symbol 键)
这样写出的深拷贝既能跳过函数、保留 Symbol,又能正确克隆 Map、Set、Date 等内置类型,且不依赖外部库。
更优解:从源头解耦数据与行为
频繁纠结“怎么拷贝函数”,往往说明设计上把逻辑和数据耦合太紧。更好的长期方案是:
- 将函数抽离为独立工具模块或服务类,通过依赖注入使用,而非塞进待拷贝的数据对象里
- 用字符串标识代替函数引用(如
validator: 'emailFormat'),在运行时查表调用真实函数 - 对 Symbol,只用作内部标记(如
obj[Symbol.for('cache')]),不参与跨环境传输
数据结构越“干净”,深拷贝就越简单可靠。函数和 Symbol 不是必须拷贝的负担,而是提示你重构边界的信号。

















