JavaScript深拷贝和浅拷贝

124 阅读3分钟

JavaScript深拷贝和浅拷贝

在JavaScript中,深拷贝和浅拷贝是处理对象或数组时的两个重要概念。它们决定了当你复制一个复杂数据类型时,修改副本是否会影响原始对象。

浅拷贝

浅拷贝是指创建一个新的对象,但新对象的属性是对原始对象属性的引用。如果原始对象的属性是基本数据类型,拷贝的是值;如果是引用类型,拷贝的是地址。

示例代码
const original = { name: "Alice", address: { city: "Wonderland" } };
const shallowCopy = { ...original };

// 修改拷贝的顶层属性
shallowCopy.name = "Bob";
console.log(original.name); // Alice

// 修改嵌套对象
shallowCopy.address.city = "Neverland";
console.log(original.address.city); // Neverland
常见浅拷贝实现方法
  1. Object.assign()

    const shallowCopy = Object.assign({}, original);
    
  2. 扩展运算符 ...

    const shallowCopy = { ...original };
    
  3. 数组的 Array.prototype.slice()concat()

    const shallowCopy = originalArray.slice();
    const shallowCopy = originalArray.concat();
    

深拷贝

深拷贝是指创建一个新的对象,并递归地复制原始对象的所有属性,包括嵌套的引用类型。深拷贝后,副本与原始对象完全独立,任何一方的修改都不会影响另一方。

示例代码
const original = { name: "Alice", address: { city: "Wonderland" } };
const deepCopy = JSON.parse(JSON.stringify(original));

// 修改深拷贝的嵌套属性
deepCopy.address.city = "Neverland";
console.log(original.address.city); // Wonderland
常见深拷贝实现方法
  1. JSON.parse(JSON.stringify())

    • 简单易用,但有局限性:

      • 忽略函数、undefinedSymbol属性。
      • 不支持循环引用。
      const deepCopy = JSON.parse(JSON.stringify(original));
      
  2. 手写递归函数

    • 对复杂对象和循环引用支持较好。

      function deepClone(obj, hash = new WeakMap()) {
          if (obj == null || typeof obj !== "object") return obj;
          if (hash.has(obj)) return hash.get(obj); // 处理循环引用
          const copy = Array.isArray(obj) ? [] : {};
          hash.set(obj, copy);
          for (const key in obj) {
              if (obj.hasOwnProperty(key)) {
                  copy[key] = deepClone(obj[key], hash);
              }
          }
          return copy;
      }
      
  3. 使用第三方库

    • 如 Lodash的cloneDeep

      import _ from "lodash";
      const deepCopy = _.cloneDeep(original);
      

对比浅拷贝和深拷贝

特性浅拷贝深拷贝
复制级别仅复制第一层递归复制所有层级
引用类型处理复制引用地址创建独立的副本
性能通常性能较优通常性能较低
使用场景适用于浅层结构或不变嵌套数据适用于复杂对象或避免原始数据污染

选择合适的拷贝方式

  • 如果数据结构较浅或无需深层次独立,使用浅拷贝(如 Object.assign...)。
  • 如果数据结构复杂或要求副本完全独立,使用深拷贝(如 JSON.parse(JSON.stringify)_.cloneDeep)。

常见面试题

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

    • 考查对递归和循环引用的处理。
    function deepClone(obj, hash = new WeakMap()) {
        if (obj == null || typeof obj !== "object") return obj;
        if (hash.has(obj)) return hash.get(obj); // 处理循环引用
        const copy = Array.isArray(obj) ? [] : {};
        hash.set(obj, copy);
        for (const key in obj) {
            if (obj.hasOwnProperty(key)) {
                copy[key] = deepClone(obj[key], hash);
            }
        }
        return copy;
    }
    
  2. JSON.parse(JSON.stringify() 为什么不总是可靠?

    • 忽略 undefined 和函数,不能处理循环引用。

    • 示例:

      const obj = { a: undefined, b: () => {}, c: Symbol("test") };
      console.log(JSON.parse(JSON.stringify(obj))); // { }
      
  3. 如何区分深拷贝和浅拷贝?

    • 给定一个嵌套对象,修改副本的嵌套属性,看原始对象是否受影响。

    • 示例:

      const original = { a: { b: 1 } };
      const shallowCopy = { ...original };
      shallowCopy.a.b = 2;
      console.log(original.a.b); // 2
      
      const deepCopy = JSON.parse(JSON.stringify(original));
      deepCopy.a.b = 3;
      console.log(original.a.b); // 1
      
  4. 第三方库(如 Lodash)的 _.cloneDeep 有哪些优点?

    • 支持循环引用和多种数据类型。

    • 示例:

      const obj = { a: 1 };
      obj.self = obj;
      const deepCopy = _.cloneDeep(obj);
      console.log(deepCopy.self === deepCopy); // true
      
  5. 性能优化:何时选择浅拷贝而非深拷贝?

    • 如果对象结构简单且无需深度独立,浅拷贝性能更优。

    • 示例:

      const arr = [1, 2, 3];
      const shallowCopy = arr.slice();