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()
七、注意事项
循环引用:必须用
WeakMap缓存已拷贝对象,避免无限递归原型链:默认不拷贝,如需保留可用
Object.setPrototypeOf()不可枚举属性:
for...in无法遍历,需用Object.getOwnPropertyDescriptors()Symbol 属性:使用
Reflect.ownKeys()或Object.getOwnPropertySymbols()获取特殊对象:
Blob、File、DOM Element等需要特殊处理
总结
深拷贝是 JavaScript 中的常见需求,选择合适的方案很重要:
- 简单场景:
JSON.parse(JSON.stringify())足够 - 现代环境:优先使用
structuredClone() - 生产环境:推荐 Lodash
_.cloneDeep() - 学习面试:掌握手动递归实现原理
理解深拷贝的原理和各种边缘情况,是 JavaScript 进阶的必备知识。