JavaScript 深拷贝完全指南

在 JavaScript 中,对象和数组是引用类型,直接赋值只会复制引用地址,而非创建独立副本。本文将全面介绍深拷贝的概念、实现方式及最佳实践。

深拷贝 vs 浅拷贝

在开始之前,先明确两者的区别:

类型说明嵌套对象
浅拷贝只复制对象的第一层属性嵌套对象仍是引用,修改会互相影响
深拷贝递归复制所有层级新旧对象完全独立,互不影响
const original = {
  name: 'Alice',
  profile: { age: 25 },
};

// 浅拷贝
const shallow = { ...original };
shallow.profile.age = 30;
console.log(original.profile.age); // 30 ❌ 原对象被修改

// 深拷贝
const deep = JSON.parse(JSON.stringify(original));
deep.profile.age = 35;
console.log(original.profile.age); // 30 ✅ 原对象不受影响

一、基础方法

1. JSON 序列化法

最简单的深拷贝方式,利用 JSON 的序列化和反序列化:

const original = {
  name: 'Alice',
  scores: [90, 85, 92],
  profile: { city: 'Beijing' },
};

const deepCopy = JSON.parse(JSON.stringify(original));

优点:

  • 简单易用,无需依赖
  • 覆盖大多数常见场景

局限性:

场景结果
Function❌ 丢失
Date❌ 变成 ISO 字符串
RegExp❌ 变成空对象 {}
Map/Set❌ 变成空对象 {}
undefined❌ 属性丢失
Symbol❌ 属性丢失
循环引用❌ 报错 TypeError
原型链❌ 不保留
// 局限性示例
const problematic = {
  fn: () => console.log('hello'),
  date: new Date(),
  regex: /pattern/gi,
  undef: undefined,
  [Symbol('key')]: 'value',
};

const copy = JSON.parse(JSON.stringify(problematic));
console.log(copy);
// { date: "2025-12-11T00:00:00.000Z", regex: {} }
// fn、undef、Symbol 属性全部丢失!

二、手动递归实现

1. 基础版:处理对象和数组

function deepClone(target) {
  // 处理 null 和非对象类型
  if (target === null || typeof target !== 'object') {
    return target;
  }

  // 创建新的容器
  const cloneTarget = Array.isArray(target) ? [] : {};

  // 递归拷贝每个属性
  for (const key in target) {
    if (Object.prototype.hasOwnProperty.call(target, key)) {
      cloneTarget[key] = deepClone(target[key]);
    }
  }

  return cloneTarget;
}

使用示例:

const original = {
  name: 'Alice',
  friends: ['Bob', 'Charlie'],
  profile: { city: 'Beijing', hobbies: ['reading', 'coding'] },
};

const copy = deepClone(original);
copy.profile.city = 'Shanghai';
console.log(original.profile.city); // 'Beijing' ✅

2. 增强版:解决循环引用 + 特殊类型

使用 WeakMap 缓存已处理的对象,解决循环引用问题:

function deepClone(target, cache = new WeakMap()) {
  // 基本类型直接返回
  if (target === null || typeof target !== 'object') {
    return target;
  }

  // 检查循环引用
  if (cache.has(target)) {
    return cache.get(target);
  }

  let cloneTarget;

  // 处理特殊类型
  if (target instanceof Date) {
    cloneTarget = new Date(target.getTime());
  } else if (target instanceof RegExp) {
    cloneTarget = new RegExp(target.source, target.flags);
  } else if (target instanceof Map) {
    cloneTarget = new Map();
    cache.set(target, cloneTarget);
    target.forEach((value, key) => {
      cloneTarget.set(deepClone(key, cache), deepClone(value, cache));
    });
    return cloneTarget;
  } else if (target instanceof Set) {
    cloneTarget = new Set();
    cache.set(target, cloneTarget);
    target.forEach((value) => {
      cloneTarget.add(deepClone(value, cache));
    });
    return cloneTarget;
  } else {
    // 普通对象或数组
    cloneTarget = Array.isArray(target) ? [] : {};
  }

  // 缓存当前对象,用于处理循环引用
  cache.set(target, cloneTarget);

  // 使用 Reflect.ownKeys 获取所有属性(包括 Symbol)
  Reflect.ownKeys(target).forEach((key) => {
    cloneTarget[key] = deepClone(target[key], cache);
  });

  return cloneTarget;
}

循环引用测试:

const obj = { name: 'test' };
obj.self = obj; // 循环引用

const copy = deepClone(obj);
console.log(copy.self === copy); // true ✅
console.log(copy !== obj); // true ✅

特殊类型测试:

const original = {
  date: new Date('2025-12-11'),
  regex: /hello/gi,
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  [Symbol('id')]: 'symbol-value',
};

const copy = deepClone(original);
console.log(copy.date instanceof Date); // true ✅
console.log(copy.regex.test('hello')); // true ✅
console.log(copy.map.get('key')); // 'value' ✅
console.log(copy.set.has(2)); // true ✅

三、工具库方案

1. Lodash _.cloneDeep()

生产环境推荐使用成熟的库,处理了各种边缘情况:

import _ from 'lodash';

const original = {
  date: new Date(),
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  nested: { deep: { value: 42 } },
};

const copy = _.cloneDeep(original);

特点:

  • ✅ 支持几乎所有类型
  • ✅ 正确处理循环引用
  • ✅ 经过大量测试,稳定可靠

2. Ramda clone()

函数式编程库 Ramda 也提供了深拷贝功能:

import { clone } from 'ramda';

const copy = clone(original);

四、原生 API:structuredClone()

ES2021 引入的原生深拷贝 API,现代浏览器和 Node.js 17+ 支持:

const original = {
  date: new Date(),
  regex: /pattern/gi,
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  buffer: new ArrayBuffer(8),
};

const copy = structuredClone(original);

支持的类型:

类型支持情况
基本类型
Array / Object
Date
RegExp
Map / Set
ArrayBuffer / TypedArray
Blob / File
循环引用
Function❌ 报错
DOM Element❌ 报错
原型链❌ 不保留

适用场景:

  • 需要完整保留复杂类型的深拷贝
  • 状态管理中的状态快照
  • 高性能深拷贝需求(原生实现,性能优秀)

五、浅拷贝方法汇总

为了对照理解,也整理一下常用的浅拷贝方法:

1. 展开运算符

const original = { a: 1, b: { c: 2 } };
const shallow = { ...original };

2. Object.assign()

const shallow = Object.assign({}, original);

3. Array.prototype.slice()

const arr = [1, 2, [3, 4]];
const shallow = arr.slice();

4. Array.from()

const shallow = Array.from(arr);

⚠️ 注意:以上方法都只是浅拷贝,嵌套对象仍然是引用!

六、性能对比与选择建议

方法性能类型支持循环引用推荐场景
JSON.parse/stringify⭐⭐⭐有限简单对象,无特殊类型
手动递归⭐⭐可扩展需实现学习理解,特殊需求
Lodash cloneDeep⭐⭐⭐全面生产环境,复杂对象
structuredClone⭐⭐⭐⭐较全面现代环境,高性能需求

选择流程

需要深拷贝?
    │
    ├── 简单对象(无函数、Date、循环引用)
    │       └── JSON.parse(JSON.stringify())
    │
    ├── 现代环境 + 无函数
    │       └── structuredClone()
    │
    └── 复杂对象 / 需要函数 / 旧环境
            └── Lodash _.cloneDeep()

七、注意事项

  1. 循环引用:必须用 WeakMap 缓存已拷贝对象,避免无限递归

  2. 原型链:默认不拷贝,如需保留可用 Object.setPrototypeOf()

  3. 不可枚举属性for...in 无法遍历,需用 Object.getOwnPropertyDescriptors()

  4. Symbol 属性:使用 Reflect.ownKeys()Object.getOwnPropertySymbols() 获取

  5. 特殊对象BlobFileDOM Element 等需要特殊处理

总结

深拷贝是 JavaScript 中的常见需求,选择合适的方案很重要:

  • 简单场景JSON.parse(JSON.stringify()) 足够
  • 现代环境:优先使用 structuredClone()
  • 生产环境:推荐 Lodash _.cloneDeep()
  • 学习面试:掌握手动递归实现原理

理解深拷贝的原理和各种边缘情况,是 JavaScript 进阶的必备知识。

← 返回列表