深拷贝和浅拷贝的区别?如何实现一个完整的深拷贝?

2024-07-21 17:14:30 147
深拷贝和浅拷贝是JavaScript中拷贝对象的两种方式。了解它们的区别以及如何实现深拷贝对于处理复杂数据结构非常重要。

浅拷贝(Shallow Copy)

浅拷贝复制对象的引用而不是对象本身,这意味着拷贝后的对象与原对象共享同一个引用。如果原对象中的某个属性是对象类型,那么浅拷贝只会复制这个引用,而不是这个对象。

常见的浅拷贝方法:

  1. Object.assign()

    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = Object.assign({}, obj1);
    
  2. 扩展运算符

    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = { ...obj1 };
    
  3. 数组的slice()方法

    const arr1 = [1, 2, { a: 3 }];
    const arr2 = arr1.slice();
    

在上述例子中,obj2arr2 中的嵌套对象 b{ a: 3 }obj1arr1 中的对应对象共享同一个引用。

深拷贝(Deep Copy)

深拷贝会递归地复制对象的所有层级,确保新对象与原对象完全独立,任何一方的修改都不会影响另一方。

常见的深拷贝方法:

  1. JSON.stringify() 和 JSON.parse()

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

    注意:这种方法有局限性,它不能拷贝函数、Date 对象、undefinedInfinityNaN等特殊值,并且无法处理循环引用。

  2. 递归方法

    这是一个更通用的方法,可以处理上述方法的局限性。

    function deepCopy(obj, hash = new WeakMap()) {
      if (obj === null || typeof obj !== 'object') {
        return obj;
      }
    
      if (hash.has(obj)) {
        return hash.get(obj);
      }
    
      let copy = Array.isArray(obj) ? [] : {};
      hash.set(obj, copy);
    
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          copy[key] = deepCopy(obj[key], hash);
        }
      }
    
      return copy;
    }
    
    const obj1 = { a: 1, b: { c: 2 }, d: function() { return 3; }, e: new Date() };
    const obj2 = deepCopy(obj1);
    

    这个 deepCopy 函数使用了递归来处理对象的每一层,并且利用 WeakMap 来处理循环引用的问题。

深拷贝和浅拷贝的区别

  • 浅拷贝

    • 复制对象的引用。
    • 如果对象的属性是对象类型,则拷贝的是引用,而不是对象本身。
    • 修改浅拷贝对象的嵌套对象会影响原对象。
  • 深拷贝

    • 递归地复制对象的所有层级,创建一个完全独立的新对象。
    • 拷贝后的对象与原对象完全独立,任何一方的修改都不会影响另一方。

实现一个完整的深拷贝

以下是一个处理更多情况的完整深拷贝函数,包括数组、对象、Date 对象、正则表达式、循环引用等:

function deepCopy(obj, hash = new WeakMap()) {
  // 如果obj是null或不是对象,直接返回它(这是基本类型的情况)
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 处理Date对象的情况
  if (obj instanceof Date) {
    return new Date(obj);
  }

  // 处理RegExp对象的情况
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }

  // 如果已经处理过这个对象,直接返回之前的拷贝(处理循环引用的情况)
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  // 创建对象或数组的拷贝
  let copy = Array.isArray(obj) ? [] : {};
  // 记录这个对象及其拷贝(用于处理循环引用)
  hash.set(obj, copy);

  // 遍历对象的所有属性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 递归拷贝每个属性
      copy[key] = deepCopy(obj[key], hash);
    }
  }

  return copy; // 返回深拷贝的对象
}

// 测试示例
const obj1 = {
  a: 1,
  b: { c: 2 },
  d: function() { return 3; },
  e: new Date(),
  f: /abc/g,
  g: [1, 2, 3],
  h: null
};

const obj2 = deepCopy(obj1);

console.log(obj2);

代码解释

  1. 基本类型处理

    if (obj === null || typeof obj !== 'object') {
      return obj;
    }
    
    • 如果 objnull 或者不是对象类型(包括基本类型),直接返回它。
  2. Date 对象处理

    if (obj instanceof Date) {
      return new Date(obj);
    }
    
    • 如果 objDate 对象,创建一个新的 Date 实例并返回。
  3. RegExp 对象处理

    if (obj instanceof RegExp) {
      return new RegExp(obj);
    }
    
    • 如果 objRegExp 对象,创建一个新的 RegExp 实例并返回。
  4. 循环引用处理

    if (hash.has(obj)) {
      return hash.get(obj);
    }
    
    • 如果 hash 中已经有这个对象,说明它是循环引用的一部分,直接返回之前的拷贝。
  5. 创建拷贝

    let copy = Array.isArray(obj) ? [] : {};
    hash.set(obj, copy);
    
    • 如果 obj 是数组,则创建一个新的空数组;否则,创建一个新的空对象。
    • 使用 WeakMap 记录这个对象及其拷贝,用于处理循环引用。
  6. 递归拷贝属性

    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        copy[key] = deepCopy(obj[key], hash);
      }
    }
    
    • 遍历 obj 的所有属性,对于每个属性递归调用 deepCopy

测试示例

const obj1 = {
  a: 1,
  b: { c: 2 },
  d: function() { return 3; },
  e: new Date(),
  f: /abc/g,
  g: [1, 2, 3],
  h: null
};

const obj2 = deepCopy(obj1);

console.log(obj2);
  • obj1 包含各种类型的数据,包括对象、数组、函数、Date 对象、正则表达式和 null
  • 调用 deepCopy 函数对 obj1 进行深拷贝,生成 obj2
  • console.log(obj2) 输出拷贝后的对象,验证深拷贝是否正确。