JavaScript中的深浅拷贝

56 阅读4分钟

1.什么是深浅拷贝

在 JavaScript 中,深拷贝和浅拷贝是用来复制对象数组的两种不同方式。

  • 浅拷贝是创建一个新的对象或数组,但是它们的内部元素仍然是原始对象或数组的引用。这意味着当修改原始对象或数组时,拷贝的对象或数组也会受到影响。

  • 深拷贝是创建一个全新的对象或数组,并且递归地复制原始对象或数组的所有内部元素。这样,即使修改原始对象或数组,拷贝的对象或数组也不会受到影响。

深浅拷贝实现的方法

以下是几种实现深拷贝和浅拷贝的常见方法:

浅拷贝

  • 对于数组,可以使用 Array.prototype.slice() 方法或者扩展运算符 ... 进行浅拷贝。这将创建一个新的数组,其中的元素是原始数组的引用。
const originalArray = [1, 2, 3];
const shallowCopy = originalArray.slice(); // 或者 const shallowCopy = [...originalArray];
originalArray[0] = 0;
console.log(originalArray); // 输出: [0, 2, 3]
console.log(shallowCopy); // 输出: [1, 2, 3]
  • 对于对象,可以使用 Object.assign() 方法或者扩展运算符 ... 进行浅拷贝。这将创建一个新的对象,其中的属性值是原始对象的引用。
const originalObject = { name: 'John', age: 30 };
const shallowCopy = Object.assign({}, originalObject); // 或者 const shallowCopy = { ...originalObject };
originalObject.name = 'Jane';
console.log(originalObject); // 输出: { name: 'Jane', age: 30 }
console.log(shallowCopy); // 输出: { name: 'John', age: 30 }

深拷贝

  • 使用 JSON 序列化和反序列化可以实现深拷贝。首先,将原始对象转换为 JSON 字符串,然后将其解析为一个新的对象。这种方法可以处理大多数简单的对象和数组,但是它有一些限制,比如无法处理函数、循环引用等。
const originalObject = { name: 'John', age: 30 };
const deepCopy = JSON.parse(JSON.stringify(originalObject));
originalObject.name = 'Jane';
console.log(originalObject); // 输出: { name: 'Jane', age: 30 }
console.log(deepCopy); // 输出: { name: 'John', age: 30 }
  • 深度递归,遍历原始对象或数组,并创建一个全新的对象或数组来存储拷贝的值。以下是一个示例实现:
function deepCopy(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj; // 如果是基本类型或者 null,则直接返回
  }
  let copy;
  if (Array.isArray(obj)) {
    copy = [];
    for (let i = 0; i < obj.length; i++) {
      copy[i] = deepCopy(obj[i]); // 递归拷贝数组元素
    }
  } else {
    copy = {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        copy[key] = deepCopy(obj[key]); // 递归拷贝对象属性
      }
    }
  }
  return copy;
}

这个 deepCopy 函数会检查传入的参数是否是基本类型或者 null,如果是的话直接返回。如果是对象或数组,则创建一个新的对象或数组,并递归地拷贝每个属性或元素。

  • 使用第三方库,比如 Lodash 的 cloneDeep() 方法,可以实现更强大和灵活的深拷贝功能。这个方法可以处理各种复杂的对象和数组,包括函数、循环引用等。
const originalObject = { name: 'John', age: 30 };
const deepCopy = _.cloneDeep(originalObject);
originalObject.name = 'Jane';
console.log(originalObject); // 输出: { name: 'Jane', age: 30 }
console.log(deepCopy); // 输出: { name: 'John', age: 30 }

需要注意的是,深拷贝可能会导致性能上的损耗,特别是在处理大型对象或嵌套层级很深的对象时。因此,在选择拷贝方法时,需要根据具体情况进行权衡。

3.与拷贝相关的知识点

  • 引用类型值类型:在 JavaScript 中,基本类型(如数字字符串布尔值等)是值类型,而对象数组函数等是引用类型。值类型的赋值是直接将复制给新变量,而引用类型的赋值是将引用复制给新变量,新变量和原变量指向同一个对象。

  • 原地修改非原地修改:有些拷贝操作是原地修改的,也就是说它们会直接修改原始对象或数组。而其他拷贝操作则是非原地修改的,它们会创建一个新的对象或数组来存储拷贝的值,而不会修改原始对象或数组。

  • 对象的浅比较深比较:浅比较是比较对象的引用,只有当两个对象引用相同的内存地址时才会被认为相等。深比较是递归地比较对象的每个属性值,只有当两个对象的属性值都相等时才会被认为相等。

  • 拷贝的性能内存消耗:深拷贝可能会导致性能上的损耗,特别是在处理大型对象或嵌套层级很深的对象时。因为深拷贝需要递归地遍历和复制每个属性或元素,所以它可能会消耗更多的内存和时间。在选择拷贝方法时,需要根据具体情况进行权衡。

  • 循环引用的处理:循环引用是指对象或数组中存在相互引用的情况。在进行深拷贝时,如果不处理循环引用,可能会导致无限递归的情况。因此,在实现深拷贝时,需要考虑如何处理循环引用,可以使用标记或缓存等方法来避免重复拷贝。

这些知识点可以帮助我们更好地理解和应用拷贝的概念,以及在实际开发中如何选择和使用适当的拷贝方法。