简介
深拷贝和浅拷贝是只针对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。