5.5 对象与数组的深拷贝技巧

5.5 对象与数组的深拷贝技巧

在 JavaScript 中,对象和数组是引用类型,直接赋值或使用扩展运算符(...)只能实现浅拷贝。深拷贝是指创建一个新的对象或数组,并递归地复制所有嵌套的对象和数组,确保修改新对象不会影响原对象。本节将介绍几种实现深拷贝的常用技巧。


5.5.1 浅拷贝 vs 深拷贝

1. 浅拷贝
浅拷贝只复制对象或数组的第一层属性,嵌套的对象或数组仍然是引用。

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

shallowCopy.b.c = 3;
console.log(original.b.c); // 输出 3(原对象被修改)

2. 深拷贝
深拷贝会递归地复制所有嵌套的对象和数组,确保新对象与原对象完全独立。

const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));

deepCopy.b.c = 3;
console.log(original.b.c); // 输出 2(原对象未被修改)

5.5.2 实现深拷贝的常用方法

1. 使用 JSON.parseJSON.stringify
这是最简单的深拷贝方法,但有以下限制:

  • 不能复制函数、undefinedSymbol 等特殊类型。
  • 不能处理循环引用(即对象内部引用自身)。
const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));

2. 使用递归函数
通过递归遍历对象或数组,手动实现深拷贝。

function deepClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  if (Array.isArray(obj)) {
    const arrCopy = [];
    for (let item of obj) {
      arrCopy.push(deepClone(item));
    }
    return arrCopy;
  }

  const objCopy = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      objCopy[key] = deepClone(obj[key]);
    }
  }
  return objCopy;
}

const original = { a: 1, b: { c: 2 } };
const deepCopy = deepClone(original);

3. 使用第三方库
许多第三方库(如 Lodash)提供了深拷贝功能,支持处理复杂对象和循环引用。

const _ = require("lodash");
const original = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(original);

5.5.3 处理特殊情况的深拷贝

1. 处理循环引用
循环引用是指对象内部引用自身,直接使用递归会导致栈溢出。可以通过缓存已拷贝的对象来解决:

function deepClone(obj, cache = new WeakMap()) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  if (cache.has(obj)) {
    return cache.get(obj);
  }

  const clone = Array.isArray(obj) ? [] : {};
  cache.set(obj, clone);

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], cache);
    }
  }
  return clone;
}

const original = { a: 1 };
original.self = original; // 循环引用
const deepCopy = deepClone(original);

2. 处理特殊类型
如果需要复制函数、undefinedSymbol 等特殊类型,可以在递归函数中增加处理逻辑:

function deepClone(obj, cache = new WeakMap()) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  if (cache.has(obj)) {
    return cache.get(obj);
  }

  const clone = Array.isArray(obj) ? [] : {};
  cache.set(obj, clone);

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], cache);
    }
  }

  // 处理 Symbol 键
  const symbols = Object.getOwnPropertySymbols(obj);
  for (let sym of symbols) {
    clone[sym] = deepClone(obj[sym], cache);
  }

  return clone;
}

5.5.4 示例代码

示例 1:使用 JSON.parseJSON.stringify

const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));

deepCopy.b.c = 3;
console.log(original.b.c); // 输出 2

示例 2:递归实现深拷贝

function deepClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  if (Array.isArray(obj)) {
    const arrCopy = [];
    for (let item of obj) {
      arrCopy.push(deepClone(item));
    }
    return arrCopy;
  }

  const objCopy = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      objCopy[key] = deepClone(obj[key]);
    }
  }
  return objCopy;
}

const original = { a: 1, b: { c: 2 } };
const deepCopy = deepClone(original);

deepCopy.b.c = 3;
console.log(original.b.c); // 输出 2

示例 3:使用 Lodash 实现深拷贝

const _ = require("lodash");
const original = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(original);

deepCopy.b.c = 3;
console.log(original.b.c); // 输出 2

5.5.5 总结

  • 浅拷贝:只复制对象或数组的第一层属性。
  • 深拷贝:递归地复制所有嵌套的对象和数组。
  • 实现方法
    • JSON.parseJSON.stringify:简单但有限制。
    • 递归函数:灵活但需要处理特殊情况。
    • 第三方库(如 Lodash):功能强大且易用。

通过掌握深拷贝的技巧,你可以更好地管理对象和数组的复制,避免意外的副作用。在接下来的学习中,我们将继续探索 JavaScript 的其他高级特性。


思考题

  1. JSON.parseJSON.stringify 实现深拷贝的局限性是什么?
  2. 如何处理循环引用的深拷贝问题?
  3. 在什么情况下应该使用第三方库实现深拷贝?
#前端开发 分享于 2025-03-19

【 内容由 AI 共享,不代表本站观点,请谨慎参考 】