深拷贝和浅拷贝

简介

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
在这里插入图片描述

赋值(Assignment):

赋值操作将一个变量的值或对象的引用复制给另一个变量。这意味着两个变量指向相同的内存地址,因此它们实际上引用的是同一个对象。当一个变量发生变化时,另一个变量也会相应地变化,因为它们都指向相同的对象。

let array1 = [1, 2, 3];
let array2 = array1;  // 赋值操作

浅拷贝(Shallow Copy):

浅拷贝创建一个新的对象,其内容与原始对象相同,但是内部的元素对象仍然是原始对象内部元素对象的引用。这意味着新对象和原始对象之间的第一层元素是独立的,但是深层嵌套的对象仍然是共享的。

let array1 = [1, [2, 3]];
let array2 = [...array1];  // 浅拷贝'

或者使用 Object.assign():

let array2 = Object.assign([], array1); // 浅拷贝

例子

let array1 = [1, [2, 3]];
let array2 = [...array1];
array2=[2]
console.log(array1);
console.log(array2);

在这里插入图片描述

let array1 = [1, [2, 3]];
let array2 = [...array1];
array2[1][0]=1;
console.log(array1);
console.log(array2);

在这里插入图片描述
现在我相信,大家可以知道什么是第一层数据改变不会影响(因为只是换了另外一个对象的指针),而浅拷贝其深层子对象其实也是其指针,如果改变其数据,原先的数据就会被改变。

深拷贝(Deep Copy):

深拷贝创建一个新的对象,并递归地复制原始对象内部的所有对象,包括嵌套对象。这意味着新对象是完全独立于原始对象的,对新对象的修改不会影响原始对象,反之亦然。

在 JavaScript 中实现深拷贝通常需要使用递归或第三方库(例如 lodash)。

let array1 = [1, [2, 3]];
let array2 = JSON.parse(JSON.stringify(array1));  // 深拷贝

或者使用第三方库:

const _ = require('lodash');
let array2 = _.cloneDeep(array1); // 深拷贝

在 JavaScript 中,需要根据具体的场景选择合适的复制方式。赋值适用于简单的对象,而对于复杂的对象,浅拷贝和深拷贝可以确保数据的独立性。
在这里插入图片描述

举个例子

const obj = {
  a: {
    c: 3,
  },
  b: 2,
};

const obj2 = {
  ...obj,
  a: {
    ...obj.a,
    c: 42,
  },
};

  • const obj = { … } 定义了一个包含嵌套对象 a 和属性 b 的对象。
  • 在创建 obj2 时,首先通过 { …obj } 创建了 obj 的浅副本。这意味着 obj2 将获得 obj 中所有顶层属性的副本,但是嵌套对象(如 obj.a)仍然是引用。
  • 然后,通过指定 a: { … },我们创建了 obj.a 的一个新的浅副本,并将其作为 a 属性的值放入 obj2。
  • 在新的 a 属性中,我们通过 { …obj.a } 复制了 obj.a 的属性,然后通过 c: 42 更新了 c 的值。
  • 最终结果是 obj2 是 obj 的一个深复制版本,其中 a.c 的值被更新为 42,而不会影响原始 obj 对象。

原始对象 obj 中已经包含了属性 a。在使用展开语法 { …obj } 复制 obj 时,其实已经包括了 a 属性。然而接下来的操作是为了更改嵌套在内部的属性 a.c 而不影响原始的 obj 对象。

这里的关键在于理解 JavaScript 的对象是引用类型,如果直接修改嵌套对象,那么原始的 obj 对象也会被影响。为了避免这种情况,我们需要创建 a 的副本,并单独修改副本上的 c 属性。

下面是代码中发生的步骤:

const obj2 = {
  ...obj, // 复制 obj 的所有顶层属性
  a: {    // 定义 obj2 的 a 属性
    ...obj.a, // 复制 obj.a 的所有属性,创建 a 的一个新副本
    c: 42,    // 在新副本上设置 c 的值为 42
  },
};

当 { …obj } 被执行时,obj2 初始化包含了 obj 的所有顶层属性(包括 a)。
然后,a: { … } 这段代码定义了 obj2 的新 a 属性。由于对象字面量中后面的属性会覆盖前面的同名属性,所以这里实际上覆盖了初始复制过来的 a 属性。
内部的 { …obj.a } 创建了 obj.a 的一个副本,因此 obj2.a 和 obj.a 指向两个不同的对象。
c: 42 表示我们将在新的 obj2.a 对象中设置 c 属性的值为 42。这改变只会影响 obj2,而不会改变原来的 obj。
综上,虽然原始的 obj 中确实包含了 a 属性,但这个语法构造使我们能够更新 obj2.a.c 的值,同时保留 obj.a 中其他不变的属性,且不会影响到 obj。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值