深拷贝
深拷贝(Deep Copy)是前端开发中的高频需求,用于创建一个对象或数组的完全独立副本,修改副本不会影响原对象。以下是深拷贝的核心实现原理、常见方案及面试要点:
一、浅拷贝 vs 深拷贝
1. 浅拷贝(Shallow Copy)
- 只复制一层对象:基本类型值直接复制,引用类型值只复制引用(共享内存)。
- 修改引用类型会影响原对象:
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj }; // 浅拷贝
shallowCopy.b.c = 3;
console.log(obj.b.c); // 3(原对象被修改)
2. 深拷贝(Deep Copy)
- 递归复制所有层级:完全创建新对象,与原对象无共享内存。
- 修改副本不影响原对象:
const obj = { a: 1, b: { c: 2 } };
const deepCopy = deepClone(obj); // 深拷贝
deepCopy.b.c = 3;
console.log(obj.b.c); // 2(原对象不受影响)
二、手动实现深拷贝函数
基础版本(处理普通对象和数组)
function deepClone(target) {
// 处理基本类型和 null
if (typeof target !== 'object' || target === null) {
return target;
}
// 处理数组
if (Array.isArray(target)) {
return target.map(item => deepClone(item));
}
// 处理对象
const clone = {};
for (const key in target) {
if (target.hasOwnProperty(key)) {
clone[key] = deepClone(target[key]);
}
}
return clone;
}
// 测试
const original = { a: 1, b: [2, 3] };
const copy = deepClone(original);
copy.b.push(4);
console.log(original.b); // [2, 3](未受影响)
进阶版本(处理特殊对象类型)
需额外处理:
- 日期对象:
new Date(target.getTime())
- 正则表达式:
new RegExp(target)
- Map/Set:递归复制键值
- 循环引用:使用 WeakMap 记录已处理对象
function deepClone(target, map = new WeakMap()) {
// 处理基本类型
if (typeof target !== 'object' || target === null) {
return target;
}
// 处理特殊对象类型
if (target instanceof Date) return new Date(target.getTime());
if (target instanceof RegExp) return new RegExp(target);
if (target instanceof Map) {
const clone = new Map();
target.forEach((value, key) => {
clone.set(key, deepClone(value, map));
});
return clone;
}
// 处理循环引用
if (map.has(target)) {
return map.get(target);
}
// 初始化克隆对象
const clone = Array.isArray(target) ? [] : {};
map.set(target, clone); // 记录已处理对象
// 递归复制所有属性
for (const key in target) {
if (target.hasOwnProperty(key)) {
clone[key] = deepClone(target[key], map);
}
}
return clone;
}
三、常见深拷贝工具函数
1. JSON.parse(JSON.stringify())
const clone = JSON.parse(JSON.stringify(original));
局限性:
- 忽略
undefined
、Symbol
、函数等类型。 - 无法处理循环引用。
- 丢失对象原型链(如
Date
会变成普通对象)。
2. Lodash 的 _.cloneDeep()
import _ from 'lodash';
const clone = _.cloneDeep(original);
优点:
- 处理所有 JS 内置对象(Date、RegExp、Map 等)。
- 解决循环引用问题。
- 高性能(优化过的递归算法)。
四、深拷贝的性能考量
-
递归层级过深:可能导致栈溢出(Stack Overflow)。
- 解决方案:改用迭代(如广度优先搜索 BFS)。
-
大数据量:递归复制会产生大量临时对象,影响性能。
- 优化建议:
- 避免对完整数据结构深拷贝,按需复制部分数据。
- 使用浅拷贝 + 变更追踪(如 React 的
immutable.js
)。
- 优化建议:
五、面试高频问题
-
如何实现一个完整的深拷贝函数?
- 答:
- 处理基本类型(直接返回)。
- 处理特殊对象(Date、RegExp、Map 等)。
- 使用 WeakMap 解决循环引用。
- 递归复制所有属性。
- 答:
-
为什么 JSON.stringify() 不适合深拷贝?
- 答:会忽略
undefined
、函数、Symbol
,无法处理循环引用,丢失对象原型。
- 答:会忽略
-
如何处理深拷贝中的循环引用?
- 答:使用 WeakMap 记录已处理的对象,遇到重复引用时直接返回缓存结果。
-
深拷贝和浅拷贝的本质区别是什么?
- 答:浅拷贝只复制一层,引用类型共享内存;深拷贝递归复制所有层级,完全独立。
六、总结
- 手动实现深拷贝:需递归处理所有属性,注意特殊对象类型和循环引用。
- 工具库:推荐使用 Lodash 的
_.cloneDeep()
或immer.js
(基于 Proxy 的不可变数据)。 - 性能优化:避免对大型数据结构深拷贝,优先使用浅拷贝 + 变更检测。
深拷贝是前端开发中不可避免的需求,理解其实现原理和局限性,有助于在实际项目中做出合理选择。