Skip to main content

深拷贝

深拷贝(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](未受影响)

进阶版本(处理特殊对象类型)

需额外处理:

  1. 日期对象new Date(target.getTime())
  2. 正则表达式new RegExp(target)
  3. Map/Set:递归复制键值
  4. 循环引用:使用 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));

局限性

  • 忽略 undefinedSymbol、函数等类型。
  • 无法处理循环引用。
  • 丢失对象原型链(如 Date 会变成普通对象)。

2. Lodash 的 _.cloneDeep()

import _ from 'lodash';
const clone = _.cloneDeep(original);

优点

  • 处理所有 JS 内置对象(Date、RegExp、Map 等)。
  • 解决循环引用问题。
  • 高性能(优化过的递归算法)。

四、深拷贝的性能考量

  1. 递归层级过深:可能导致栈溢出(Stack Overflow)。

    • 解决方案:改用迭代(如广度优先搜索 BFS)。
  2. 大数据量:递归复制会产生大量临时对象,影响性能。

    • 优化建议
      • 避免对完整数据结构深拷贝,按需复制部分数据。
      • 使用浅拷贝 + 变更追踪(如 React 的 immutable.js)。

五、面试高频问题

  1. 如何实现一个完整的深拷贝函数?

    • 答:
      1. 处理基本类型(直接返回)。
      2. 处理特殊对象(Date、RegExp、Map 等)。
      3. 使用 WeakMap 解决循环引用。
      4. 递归复制所有属性。
  2. 为什么 JSON.stringify() 不适合深拷贝?

    • 答:会忽略 undefined、函数、Symbol,无法处理循环引用,丢失对象原型。
  3. 如何处理深拷贝中的循环引用?

    • 答:使用 WeakMap 记录已处理的对象,遇到重复引用时直接返回缓存结果。
  4. 深拷贝和浅拷贝的本质区别是什么?

    • 答:浅拷贝只复制一层,引用类型共享内存;深拷贝递归复制所有层级,完全独立。

六、总结

  • 手动实现深拷贝:需递归处理所有属性,注意特殊对象类型和循环引用。
  • 工具库:推荐使用 Lodash 的 _.cloneDeep()immer.js(基于 Proxy 的不可变数据)。
  • 性能优化:避免对大型数据结构深拷贝,优先使用浅拷贝 + 变更检测。

深拷贝是前端开发中不可避免的需求,理解其实现原理和局限性,有助于在实际项目中做出合理选择。