JavaScript 深浅拷贝详解
一、为什么需要拷贝?
JavaScript 中数据类型分为两类:
- 基本类型(
Number、String、Boolean、null、undefined、Symbol、BigInt):存储在栈中,赋值即复制值。 - 引用类型(
Object、Array、Function等):栈中存的是地址,堆中存的是数据,赋值复制的是地址。
const a = { name: 'Tom' };
const b = a;
b.name = 'Jerry';
console.log(a.name); // 'Jerry' ← a 也被改了
b = a 仅复制了引用地址,两者指向同一对象。要避免这种联动,就需要"拷贝"。
二、浅拷贝(Shallow Copy)
只拷贝第一层属性。如果属性值是引用类型,仍然共享地址。
1. Object.assign
const obj = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, obj);
copy.a = 99; // 不影响 obj
copy.b.c = 99; // 影响 obj.b.c ← 第二层共享
2. 扩展运算符 ...
const copy = { ...obj };
const arrCopy = [...arr];
行为与 Object.assign 一致,只拷贝一层。
3. 数组方法 slice / concat
const arr = [1, [2, 3]];
const copy = arr.slice();
copy[1][0] = 99; // arr 也变 [1, [99, 3]]
手写一个浅拷贝
function shallowCopy(target) {
if (target === null || typeof target !== 'object') return target;
const result = Array.isArray(target) ? [] : {};
for (const key in target) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
result[key] = target[key];
}
}
return result;
}
三、深拷贝(Deep Copy)
递归拷贝所有层级,新旧对象完全独立。
1. JSON.parse(JSON.stringify(obj))
最常见的"偷懒"写法:
const copy = JSON.parse(JSON.stringify(obj));
优点: 简洁。 缺点(坑很多):
undefined、function、Symbol会被丢弃或转nullDate变成字符串RegExp、Map、Set变成{}NaN、Infinity变成null- 不能处理循环引用(直接报错)
2. structuredClone(推荐 ✅)
浏览器和 Node.js 17+ 原生支持:
const copy = structuredClone(obj);
支持 Date、Map、Set、RegExp、循环引用等,目前最优解。
不能拷贝函数和 DOM 节点。
3. 第三方库
lodash.cloneDeep(obj)— 兼容性好,处理边界场景全面。
4. 手写一个深拷贝
要点:递归 + 处理引用类型 + 防循环引用。
function deepClone(target, hash = new WeakMap()) {
if (target === null || typeof target !== 'object') return target;
if (target instanceof Date) return new Date(target);
if (target instanceof RegExp) return new RegExp(target);
if (hash.has(target)) return hash.get(target); // 循环引用
const result = Array.isArray(target) ? [] : {};
hash.set(target, result);
Reflect.ownKeys(target).forEach(key => {
result[key] = deepClone(target[key], hash);
});
return result;
}
关键点:
WeakMap缓存已拷贝对象,解决循环引用Reflect.ownKeys同时拿到字符串键和Symbol键- 单独处理
Date、RegExp等特殊对象
四、对比总结
| 方式 | 深/浅 | 函数 | Date | 循环引用 | Symbol |
|---|---|---|---|---|---|
Object.assign / ... | 浅 | ✅ | ✅ | ✅ | ✅ |
JSON 大法 | 深 | ❌ | ❌字符串 | ❌报错 | ❌ |
structuredClone | 深 | ❌ | ✅ | ✅ | ✅ |
lodash.cloneDeep | 深 | ✅ | ✅ | ✅ | ✅ |
手写 deepClone | 深 | 视实现 | 视实现 | ✅ | ✅ |
五、选型建议
- 只需一层独立 → 扩展运算符
... - 现代环境深拷贝 →
structuredClone - 数据是纯 JSON →
JSON.parse(JSON.stringify(...))够用 - 复杂场景 / 老环境 →
lodash.cloneDeep - 理解原理 / 面试 → 手写
deepClone