【干货】你真的会深拷贝和浅拷贝吗?JavaScript 这些知识点你一定要掌握!

在 JavaScript 开发中,我们经常需要复制对象或数组。但你有没有想过,简单的赋值操作背后,隐藏着深拷贝和浅拷贝的秘密?如果理解不透彻,你的代码很可能埋下难以察觉的 Bug。今天,我们就来彻底搞懂 JavaScript 的深拷贝和浅拷贝!

什么是浅拷贝?

浅拷贝,顾名思义,只拷贝对象或数组的“表面”一层。它会创建一个新的对象或数组,但新对象/数组中的属性或元素,如果本身是引用类型(例如对象、数组),则仍然指向原始对象/数组的内存地址。

浅拷贝的特点:

  • 只复制第一层属性/元素。
  • 如果属性/元素是引用类型,则复制的是引用地址,而不是值本身。
  • 修改新对象/数组的引用类型属性/元素,会影响原始对象/数组。

浅拷贝的常见方式:

  1. 直接赋值(=)

    let obj1 = { a: 1, b: { c: 2 } };
    let obj2 = obj1; // 浅拷贝
    
    obj2.a = 3;
    obj2.b.c = 4;
    
    console.log(obj1); // 输出:{ a: 3, b: { c: 4 } }
    console.log(obj2); // 输出:{ a: 3, b: { c: 4 } }
    
  2. Object.assign()

    let obj1 = { a: 1, b: { c: 2 } };
    let obj2 = Object.assign({}, obj1); // 浅拷贝
    
    obj2.a = 3;
    obj2.b.c = 4;
    
    console.log(obj1); // 输出:{ a: 1, b: { c: 4 } }
    console.log(obj2); // 输出:{ a: 3, b: { c: 4 } }
    
  3. 展开运算符(...)

    let obj1 = { a: 1, b: { c: 2 } };
    let obj2 = { ...obj1 }; // 浅拷贝
    
    obj2.a = 3;
    obj2.b.c = 4;
    
    console.log(obj1); // 输出:{ a: 1, b: { c: 4 } }
    console.log(obj2); // 输出:{ a: 3, b: { c: 4 } }
    
  4. 数组的 slice()concat()

    let arr1 = [1, { a: 2 }];
    let arr2 = arr1.slice(); // 浅拷贝
    // let arr2 = arr1.concat(); // 浅拷贝
    
    arr2[0] = 3;
    arr2[1].a = 4;
    
    console.log(arr1); // 输出:[1, { a: 4 }]
    console.log(arr2); // 输出:[3, { a: 4 }]
    

什么是深拷贝?

深拷贝会创建一个全新的对象或数组,新对象/数组中的所有属性或元素,包括引用类型,都会被递归地复制一份新的,而不是指向原始对象的内存地址。

深拷贝的特点:

  • 复制所有层级的属性/元素。
  • 如果属性/元素是引用类型,则复制的是值本身,而不是引用地址。
  • 修改新对象/数组的任何属性/元素,都不会影响原始对象/数组。

深拷贝的常见方式:

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

    let obj1 = { a: 1, b: { c: 2 }, d: undefined, e: function() {} };
    let obj2 = JSON.parse(JSON.stringify(obj1)); // 深拷贝
    
    obj2.a = 3;
    obj2.b.c = 4;
    
    console.log(obj1); // 输出:{ a: 1, b: { c: 2 }, d: undefined }
    console.log(obj2); // 输出:{ a: 3, b: { c: 4 } }
    

    注意:

    • 这种方法虽然简单,但存在一些局限性:
      • 无法处理 undefinedfunctionSymbol 等特殊类型,会丢失。
      • 无法处理循环引用的对象,会报错。
      • 无法处理 Date 对象,会变成字符串。
  2. 递归实现深拷贝

    function deepClone(obj) {
      if (typeof obj !== 'object' || obj === null) {
        return obj; // 如果不是对象或为null,直接返回
      }
    
      const clonedObj = Array.isArray(obj) ? [] : {}; // 创建新对象或数组
    
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          clonedObj[key] = deepClone(obj[key]); // 递归调用
        }
      }
    
      return clonedObj;
    }
    
    let obj1 = { a: 1, b: { c: 2 } };
    let obj2 = deepClone(obj1); // 深拷贝
    
    obj2.a = 3;
    obj2.b.c = 4;
    
    console.log(obj1); // 输出:{ a: 1, b: { c: 2 } }
    console.log(obj2); // 输出:{ a: 3, b: { c: 4 } }
    

    注意:

    • 这种方法可以处理循环引用,但需要注意性能问题,对于大型对象可能会比较慢。

总结:

  • 浅拷贝只复制对象/数组的第一层,引用类型属性/元素仍然指向原始对象/数组的内存地址。
  • 深拷贝会递归复制所有层级的属性/元素,包括引用类型,创建全新的对象/数组。
  • 选择浅拷贝还是深拷贝,取决于你的具体需求。
  • JSON.parse(JSON.stringify()) 简单但有局限性。
  • 递归实现深拷贝可以处理循环引用,但要注意性能。

结尾:

掌握深拷贝和浅拷贝是 JavaScript 开发的基本功。希望通过这篇文章,你能够彻底理解它们,并在实际开发中灵活运用,避免不必要的 Bug。如果你觉得这篇文章对你有帮助,请分享给你的朋友们吧!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容