JavaScript的深拷贝浅拷贝详解

JavaScript是一种非常灵活的编程语言,它具备很多特性,其中最为重要的特性之一,就是对象的引用传递。在JavaScript中,所有的基本数据类型都是值传递,即在对一个变量赋值时,将其值复制给另一个变量。而对于复杂数据类型(对象、数组等),则是采用引用传递的方式,即将对象的“引用”复制给另一个变量。这意味着当我们对复杂数据类型进行操作时,很可能会影响到原数据,因为这两个变量实际上指向同一块内存空间。因此,深拷贝与浅拷贝就成为了js编程中不可避免的问题。下面我们将从多个方面对深拷贝与浅拷贝做详细的阐述。

一、浅拷贝

1、定义:

浅拷贝是指复制一个对象的引用,并不是复制对象本身。在JavaScript中,我们可以使用Object.assign()方法实现对象的浅拷贝。

    
let obj1 = { a: 0, b: { c: 0 } };
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }

obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }

obj2.a = 2;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 0 } }

obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 3 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3 } }
    

由上面的代码可以看出,使用Object.assign()方法可以实现对象的浅拷贝,当只是拷贝了第一层的数据时,obj1和obj2并不会互相影响。但是,当拷贝了第二层及其以上层次时,obj1和obj2就会产生影响,因为对象的属性b被拷贝的时候,并没有复制其引用指向的内存空间,而是复制了其引用值,这就导致obj1和obj2的属性b所指向的内存空间是相同的。因此,当我们修改属性b的值时,两个变量会产生相同的变化,这就是典型的浅拷贝问题。

2、浅拷贝的应用场景:

浅拷贝适用于不包含引用类型数据的对象或者数组。

二、深拷贝

1、定义:

深拷贝是指对一个对象的完全拷贝,即便这个对象的属性值也是引用类型,也能够递归地拷贝子对象的所有属性。在JavaScript中,我们可以使用JSON.parse(JSON.stringify(obj))方式实现对象的深拷贝。

    
let obj1 = { a: 0, b: { c: 0 } };
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }

obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }

obj2.a = 2;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 0 } }

obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3 } }
    

使用JSON.parse(JSON.stringify(obj))方式可以实现对象的深拷贝,即使对象的属性值也是引用类型,也能够递归地拷贝子对象的所有属性。由此可见,深拷贝可以真正意义上地将两个对象相互独立开来,互不影响。

2、深拷贝的应用场景:

深拷贝适用于包含引用类型数据的对象或者数组。

三、深浅拷贝的实现方式

1、基于JSON.stringify()和JSON.parse()的深拷贝实现:

    
function deepClone_Json(obj) {
  let str = JSON.stringify(obj);
  return JSON.parse(str);
}

let obj1 = { a: 0, b: { c: 0 } };
let obj2 = deepClone_Json(obj1);

obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }

obj2.a = 2;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 0 } }

obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3 } }
    

通过重写JSON.stringify()和JSON.parse()方法可以实现深拷贝,但是这种方法存在以下几个问题:

  • 该方法无法拷贝函数,RegExp对象等特殊对象。
  • 在部分特殊场景下,该方法可能会抛出异常。
  • 该方法无法解决循环引用的问题。

2、递归方式的深拷贝实现:

    
function deepClone_Recursion(obj) {
  if (typeof obj !== "object" || obj === null) {
    return obj;
  }
  let newObj = obj instanceof Array ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = deepClone_Recursion(obj[key]);
    }
  }
  return newObj;
}

let obj1 = { a: 0, b: { c: 0 } };
let obj2 = deepClone_Recursion(obj1);

obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }

obj2.a = 2;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 0 } }

obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3 } }
    

通过递归的方式实现深拷贝,可以解决前面所提到的问题,但是这种方法在处理循环引用的时候,难免会陷入死循环,导致浏览器崩溃。

3、利用WeakMap解决循环引用的深拷贝实现:

    
function deepClone(obj, map = new WeakMap()) {
  if (typeof obj !== "object" || obj === null) {
    return obj;
  }
  if (map.has(obj)) {
    return map.get(obj);
  }
  let newObj = obj instanceof Array ? [] : {};
  map.set(obj, newObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = deepClone(obj[key], map);
    }
  }
  return newObj;
}

let obj1 = { a: 0, b: { c: 0 } };
let obj2 = { a: obj1, b: obj1 };
obj1.c = obj2;

let obj3 = deepClone(obj1);

obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 }, "c": { "a": {...}, "b": {...} } }
console.log(JSON.stringify(obj3)); // { "a": 0, "b": { "c": 0 }, "c": { "a": {...}, "b": {...} } }
    

利用WeakMap来解决循环引用问题的深拷贝实现方式是目前最优的解决方案之一。WeakMap是ES6新增的一种Map类型,与Map类型相比,WeakMap只能用对象作为键,当对象被垃圾回收机制回收时,相应的键值对也会从WeakMap中删除,从而避免内存泄漏。通过在递归过程中维护一个map,我们可以记录已经拷贝过的对象,避免循环引用陷入死循环。

四、总结

深拷贝与浅拷贝是JavaScript编程中不可避免的问题,尤其是在处理对象和数组时尤为突出。深浅拷贝的实现方式也有多种,每种方式都有其优缺点。在实际开发中,我们需要根据具体场景来选择最适合的拷贝方式,避免因为深浅拷贝问题导致程序出现不可预期的错误。

原创文章,作者:DTANP,如若转载,请注明出处:https://www.506064.com/n/334262.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
DTANP的头像DTANP
上一篇 2025-02-05 13:05
下一篇 2025-02-05 13:05

相关推荐

  • 使用JavaScript日期函数掌握时间

    在本文中,我们将深入探讨JavaScript日期函数,并且从多个视角介绍其应用方法和重要性。 一、日期的基本表示与获取 在JavaScript中,使用Date对象来表示日期和时间,…

    编程 2025-04-28
  • JavaScript中使用new Date转换为YYYYMMDD格式

    在JavaScript中,我们通常会使用Date对象来表示日期和时间。当我们需要在网站上显示日期时,很多情况下需要将Date对象转换成YYYYMMDD格式的字符串。下面我们来详细了…

    编程 2025-04-27
  • JavaScript中修改style属性的方法和技巧

    一、基本概念和方法 style属性是JavaScript中一个非常重要的属性,它可以用来控制HTML元素的样式,包括颜色、大小、字体等等。这里介绍一些常用的方法: 1、通过Java…

    编程 2025-04-25
  • Linux sync详解

    一、sync概述 sync是Linux中一个非常重要的命令,它可以将文件系统缓存中的内容,强制写入磁盘中。在执行sync之前,所有的文件系统更新将不会立即写入磁盘,而是先缓存在内存…

    编程 2025-04-25
  • 神经网络代码详解

    神经网络作为一种人工智能技术,被广泛应用于语音识别、图像识别、自然语言处理等领域。而神经网络的模型编写,离不开代码。本文将从多个方面详细阐述神经网络模型编写的代码技术。 一、神经网…

    编程 2025-04-25
  • git config user.name的详解

    一、为什么要使用git config user.name? git是一个非常流行的分布式版本控制系统,很多程序员都会用到它。在使用git commit提交代码时,需要记录commi…

    编程 2025-04-25
  • MPU6050工作原理详解

    一、什么是MPU6050 MPU6050是一种六轴惯性传感器,能够同时测量加速度和角速度。它由三个传感器组成:一个三轴加速度计和一个三轴陀螺仪。这个组合提供了非常精细的姿态解算,其…

    编程 2025-04-25
  • Java BigDecimal 精度详解

    一、基础概念 Java BigDecimal 是一个用于高精度计算的类。普通的 double 或 float 类型只能精确表示有限的数字,而对于需要高精度计算的场景,BigDeci…

    编程 2025-04-25
  • 详解eclipse设置

    一、安装与基础设置 1、下载eclipse并进行安装。 2、打开eclipse,选择对应的工作空间路径。 File -> Switch Workspace -> [选择…

    编程 2025-04-25
  • Python安装OS库详解

    一、OS简介 OS库是Python标准库的一部分,它提供了跨平台的操作系统功能,使得Python可以进行文件操作、进程管理、环境变量读取等系统级操作。 OS库中包含了大量的文件和目…

    编程 2025-04-25

发表回复

登录后才能评论