javascript前端深拷贝

对js的一段深拷贝代码的解读

通过这段代码,我们实现了一个简洁且高效的深拷贝方法。基本类型直接返回:如果对象是基本数据类型(如nullnumberstring等),直接返回它。数组的深拷贝:使用map遍历数组中的每个元素,对每个元素递归地进行深拷贝,确保每个元素都是独立的。对象的深拷贝:使用reduce遍历对

2024-11-19·阅读约 9 分钟·计算中...

在 JavaScript 中,深拷贝(Deep Copy)是指创建一个对象或数组的完全副本,并确保原始数据和副本之间没有引用关系。与浅拷贝不同,深拷贝会递归复制对象中的每一层数据,确保内层对象的引用被独立出来,避免在修改副本时影响原始数据。

下面我们就来深入讲解一段实现深拷贝的代码:

function deepCopy(obj) {
  // 如果 obj 为 null 或基本类型,直接返回
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 处理数组
  if (Array.isArray(obj)) {
    return obj.map(item => deepCopy(item)); // 使用 map 简化数组的深拷贝
  }

  // 处理对象
  return Object.keys(obj).reduce((acc, key) => {
    acc[key] = deepCopy(obj[key]);
    return acc;
  }, {}); // 使用 reduce 简化对象的深拷贝
}

代码解析

1. if (obj === null || typeof obj !== 'object')

这一行检查 obj 是否是 null 或者基本数据类型。如果是,则直接返回 obj 本身。

  • obj === null:首先检查对象是否为 null,因为 null 在 JavaScript 中被认为是一个特殊的类型。 null 不属于对象类型,它是基本类型之一。返回 null 可以防止后续操作中的错误。
  • typeof obj !== 'object':接着检查 obj 是否是对象。JavaScript 中的 typeof 操作符会将基本数据类型(如 stringnumberboolean 等)返回为对应的字符串,而对对象(包括数组和函数)则返回 'object'。如果 obj 不是对象类型(即它是一个基本类型数据),直接返回该数据,不需要做进一步的处理。

为何这么做?

  • 基本数据类型(如数字、字符串、布尔值等)是原始值,它们不会被引用,因此不需要拷贝。直接返回原始数据即可。
  • null 由于typeof null返回’object’,而null又不是对象,因此应当特殊处理。

2. if (Array.isArray(obj))

这部分判断 obj 是否是数组。如果是数组,则调用 map 方法对数组中的每个元素进行递归深拷贝。

  • Array.isArray(obj):这是 JavaScript 提供的用于判断一个变量是否是数组的方法。如果 obj 是数组,Array.isArray 返回 true,否则返回 false
  • obj.map(item => deepCopy(item)):对于数组的处理,我们使用 map 方法遍历数组中的每一项,对每个元素进行递归调用 deepCopy。这样做可以确保数组中每一个对象或数组都会被深拷贝,而不仅仅是复制引用。

为何使用 map

  • map 用来遍历数组,并且会返回一个新的数组。它非常适合我们对数组元素进行操作并返回新数组的场景。通过 deepCopy(item),我们确保数组中的每个元素都被深拷贝,不会影响原始数组中的数据。

3. return Object.keys(obj).reduce((acc, key) => { ... }, {});

这部分代码用于处理对象的深拷贝。对象是由键值对构成的,深拷贝对象时,我们需要逐一遍历对象的每个属性,并对每个属性值进行深拷贝。

  • Object.keys(obj)Object.keys 会返回对象 obj 中所有可枚举属性名的数组。例如,假设 obj = {a: 1, b: {c: 2}},那么 Object.keys(obj) 会返回 ["a", "b"]
  • reducereduce 是数组的高阶方法,用来累积计算一个结果。在这里,我们使用 reduce 来遍历对象的属性,将每个属性值深拷贝后加入到一个新的对象 acc 中。
Object.keys(obj).reduce((acc, key) => {
  acc[key] = deepCopy(obj[key]);
  return acc;
}, {});
  • (acc, key)acc 是累加器,它将存储最终的深拷贝结果;key 是当前正在处理的属性名。

  • acc[key] = deepCopy(obj[key]):对每个属性值进行深拷贝并赋值给新对象 acc。这样,acc 就包含了深拷贝后的所有属性。

  • return acc:每次循环后返回累加器,继续进行下一次循环。

  • {}reduce 方法的第二个参数是初始值,{} 是一个空对象,表示我们将从一个空对象开始构建深拷贝结果。

  • 代码执行流程

    • 假设有如下的对象 obj

      const obj = {
        a: 1,
        b: { x: 10, y: 20 },
        c: [1, 2, 3]
      };
      

      执行 Object.keys(obj) 会得到一个包含所有属性名的数组:

    • Object.keys(obj); // ["a", "b", "c"]
      

      然后,reduce 会依次遍历这个数组:

    • 第一次迭代

      • key = "a"obj["a"] = 1
      • deepCopy(1) 直接返回 1
      • acc = { a: 1 }
    • 第二次迭代

      • key = "b"obj["b"] = { x: 10, y: 20 }
      • deepCopy({ x: 10, y: 20 }) 会创建一个新的对象 { x: 10, y: 20 }
      • acc = { a: 1, b: { x: 10, y: 20 } }
    • **第三次迭代**:

      • `key = "c"`,`obj["c"] = [1, 2, 3]`
      • `deepCopy([1, 2, 3])` 会创建一个新的数组 `[1, 2, 3]`
      • `acc = { a: 1, b: { x: 10, y: 20 }, c: [1, 2, 3] }`
    • 最终,`reduce` 返回的新对象 `acc` 就是:

      {
        a: 1,
        b: { x: 10, y: 20 },
        c: [1, 2, 3]
      }
      
    • 这个新对象是原对象的深拷贝,其中的每个属性值都是深拷贝后的结果。**为何使用 `reduce`?**

  • reduce 非常适合用于逐步生成一个新的对象。在处理对象深拷贝时,我们可以通过 reduce 将对象的每个属性和值复制到一个新的对象中。

  • reduce 的优点在于,它可以在一个步骤中处理并返回一个最终结果,这使得代码更加简洁高效。

总结

通过这段代码,我们实现了一个简洁且高效的深拷贝方法。以下是关键点:

  1. 基本类型直接返回:如果对象是基本数据类型(如 nullnumberstring 等),直接返回它。
  2. 数组的深拷贝:使用 map 遍历数组中的每个元素,对每个元素递归地进行深拷贝,确保每个元素都是独立的。
  3. 对象的深拷贝:使用 reduce 遍历对象的所有键,递归拷贝对象的每个属性值,确保每个属性值都是独立的。

这段代码通过递归和 mapreduce 的巧妙使用,实现了对复杂嵌套对象和数组的深拷贝,避免了浅拷贝带来的引用共享问题,是一种在 JavaScript 中实现深拷贝的常用方式。

订阅 FreeMac

每周精选:Mac 高效技巧、免费替代付费软件、开发者工具推荐。用对你的 MacBook,省钱 + 提效。