JavaScript深拷贝如何实现_与浅拷贝有什么区别【教程】

JavaScript无内置深拷贝函数;JSON序列化会丢失函数、undefined等;structuredClone()是目前最接近标准的原生方案,支持Date、Map、Set等但不支持function、Symbol及自定义类实例。

JavaScript 中没有内置的“深拷贝”函数,JSON.parse(JSON.stringify(obj)) 看似简单,但会丢函数、undefined、Symbol、循环引用、Date、RegExp 等,不能算真正可靠的深拷贝。

浅拷贝只复制第一层引用,深拷贝递归复制所有层级

浅拷贝(如 Object.assign()、展开运算符 {...obj}Array.prototype.slice())只复制对象/数组的顶层属性,嵌套对象仍共享内存地址。修改嵌套值会影响原对象。

深拷贝则递归遍历每个属性,为每个对象或数组新建内存空间,彻底隔离修改影响。

常见误判场景:

  • structuredClone() 但没检查浏览器兼容性(Chrome 98+、Firefox 94+ 支持,Safari 15.4+ 部分支持)
  • 以为 lodash.cloneDeep() 能处理所有类型,其实对某些自定义类实例或带不可枚举属性的对象仍有局限
  • 在 Node.js 中直接用 require('util').inspect()vm.createContext() 模拟深拷贝——完全不推荐,语义错误且极不稳定

structuredClone() 是目前最接近标准的深拷贝方案

这是 ECMAScript 提案已落地的原生 API,能正确处理 DateMapSetRegExpArrayBufferTypedArrayError(部分)、以及嵌套结构和循环引用(自动跳过或报错,取决于环境)。

但它不支持:

  • functionundefinedSymbol
  • 带有 prototype 的自定义类实例(只保留数据,丢失方法)
  • DOM 节点、windowdocument 等宿主对象
const original = {
  a: 1,
  b: { c: 2 },
  d: new Date(),
  e: /test/g,
  f: new Set([1, 2])
};
const copy = structuredClone(original);
copy.b.c = 99;
console.log(original.b.c); // 2 —— 不受影响

手动实现简易深拷贝需规避循环引用和类型判断

手写时最常被忽略的是循环引用(对象属性指向自身或形成环),不处理会导致栈溢出。必须用 WeakMap 缓存已拷贝过的对象引用。

基础逻辑要点:

  • typeof + Array.isArray() + Object.prototype.toString.call() 组合判断类型
  • null、基本类型(string/number/boolean/bigint/symbol)直接返回
  • DateRegExp 等特殊对象调用构造函数重建(new Date(obj)
  • 对普通对象/数组,先新建空实例,再递归拷贝每个键值,并用 WeakMap 记录映射关系防死循环
function deepClone(obj, map = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (map.has(obj)) return map.get(obj);
  
  const cloned = Array.isArray(obj) ? [] : {};
  map.set(obj, cloned);
  
  for (const key in obj) {
    if (Object.prototype.h

asOwnProperty.call(obj, key)) { cloned[key] = deepClone(obj[key], map); } } return cloned; }

生产环境优先选 structuredClone(),降级用 lodash 或自研方案要明确边界

不要为了“看起来通用”而强行统一所有场景的深拷贝逻辑。比如后端传来的纯 JSON 数据,JSON.parse(JSON.stringify(x)) 完全够用;但涉及用户上传的富文本状态(含 MapSet)就必须用 structuredClone()

最容易被忽略的一点:深拷贝不是免费的。嵌套越深、数据越大,性能开销越明显。如果只是临时读取配置,考虑是否真的需要深拷贝——有时 Object.freeze() + 不可变更新更轻量、更可控。