深拷贝是隔离业务逻辑与框架元数据的关键防线,通过跳过\_、$、\_\_开头属性及不可枚举元数据,确保业务对象纯净,避免序列化错误、接口污染和测试失败。

深拷贝是隔离业务逻辑与框架元数据的关键防线。它不改变框架行为,而是确保业务代码拿到的是“干净副本”,所有框架注入的字段(如 _v_isRef、$$raw、__ob__、$$state 等)不会泄漏到业务对象中,也不会被业务逻辑意外修改。
为什么元数据混入会导致问题
现代前端框架(Vue 3 / Pinia / Zustand)和状态库常在对象上添加不可枚举、私有或响应式标记属性。这些元数据服务于框架内部机制,但若业务层直接操作原始引用,就可能出现:
- 序列化时意外包含
__v_skip或$$typeof,导致接口报错或日志污染 - 调用
JSON.stringify()前未清理,输出冗余字段甚至抛出循环引用错误 - 表单提交、缓存写入、导出 Excel 时,把框架内部字段当业务字段一并发送
- 单元测试断言失败——因为预期的是纯业务结构,实际却混入了
__isVue
深拷贝如何实现元数据剥离
真正有效的深拷贝不是简单复制所有键值,而是跳过框架注入的非业务属性。推荐两种落地方式:
-
构造时过滤:在封装的深拷贝工具中内置白名单机制,只保留
Object.prototype原生可枚举属性,忽略以_、$、__开头的属性,以及Symbol.toStringTag等特殊 Symbol 键 -
反序列化净化:使用
structuredClone()(支持 Map/Set/Date/RegExp,且天然忽略不可枚举属性和原型方法),或自定义JSON.parse(JSON.stringify(obj))的替代方案(配合replacer函数剔除元数据字段)
典型场景中的使用姿势
不是所有地方都需要深拷贝,关键看数据流向是否跨“信任边界”:
-
从 store 取值后用于 API 提交:对
useUserStore().profile执行深拷贝,再删减/补充字段,避免$$state被发出去 -
组件 props 接收响应式对象:在
onMounted或计算属性中主动深拷一份,后续所有格式化、排序、筛选都在副本上进行 -
缓存中间结果(如搜索建议):深拷贝 + 元数据剥离后再存入
localStorage或 LRU 缓存,防止下次读取时触发 Vue 的 getter 副作用
注意避开常见误区
深拷贝本身不能自动识别“哪些是元数据”,它依赖实现逻辑。以下做法容易失效:
- 仅用
structuredClone:虽安全,但不处理 Vue Ref 或自定义 Proxy 对象,遇到ref({})会抛错 - 依赖
lodash.cloneDeep默认行为:它会复制所有自有属性,包括__v_skip,需配合customizer过滤 - 在 computed 中无条件深拷贝:造成不必要的性能开销,应只在真正需要脱离响应式链时才做










