深拷贝、浅拷贝与引用赋值

52 阅读3分钟

写这篇文章是想记录自己一直以来的一个误区,希望给有过同样疑惑的学习者一个参考。

误区:修改浅拷贝对象值一定会影响原对象

我之前一直认为浅拷贝就是把原对象的内存地址指针赋值给新的变量,即这两个变量共享一块内存,由此改变其中任何一个的值,另一个也会同步修改。

我的构想是:

let obj = { a: 1, b: 2 };
let obj1 = obj;
obj1.a = 2; // 修改拷贝后的对象,obj1对象值变为{a:2,b:2}
// 原数组obj的值也变为{a:2,b:2}

实际上,上述的构想确实没问题,这两个对象的值会同步修改,但是"="直接赋值不是浅拷贝,而是引用赋值

对于浅拷贝,修改拷贝后的值是否会影响原对象的值取决于属性值的数据类型,如果属性值是基本数据类型(String, Number, Boolean, null, undefined, Symbol, BigInt),则修改属性值并不会影响到原对象的相应属性值;如果属性值是引用数据类型(Object, Array, Function),则修改属性值会导致原对象相应属性值同步改变。例如:

let obj = { a: 1, b: { c: 2 } };
let obj1 = { ...obj };
obj1.a = 2; // 这个修改不会影响原对象obj,因为a是基本类型Number
obj1.b.c = 3; // 这个修改会影响原对象obj,因为b是引用类型。

上面这段代码就是浅拷贝影响原对象的例子,可以看到,并不是修改浅拷贝的对象值就一定会影响原对象,是否影响取决于被修改的属性值的数据类型。

所以我之前是一直把引用赋值和浅拷贝混为一谈了,实际上这两种情况是有区别的。引用赋值相当于实际上只有一个对象,只是把这个对象的内存地址指针复制给了两个变量;而浅拷贝则是创建了一个新对象,随后把原对象的每个元素一一赋值到新对象中,实际存在两个对象

下面深拷贝与浅拷贝的定义与常用实现。

浅拷贝

定义

浅拷贝只复制对象的第一层属性。如果属性是基本类型(如 String, Number, Boolean, null, undefined, Symbol, BigInt),则直接复制其值。如果属性是引用类型(如 Object, Array, Function),则复制的是其内存地址(引用),而不是实际的值。

实现

  1. 使用扩展运算符"..."
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };
  1. Object.assign
const original = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, original);
  1. 数组slice方法
const originalArray = [1, 2, { name: 'John' }];
const shallowCopyArray = originalArray.slice();
  1. 数组concat方法
const originalArray = [1, 2, { name: 'John' }];
const shallowCopyArray = originalArray.concat();

深拷贝

定义

深拷贝是创建一个全新的、完全独立的对象。它会递归地复制原对象的所有层级属性,包括所有嵌套的引用类型。最终,新对象和原对象在内存中完全分离,没有任何共享的引用。

实现

  1. JSON.parse(JSON.stringfy(obj))

这是深拷贝最简单也最常用的方法,但是这种方法存在一些局限:

  • 无法拷贝 undefined,Function,Symbol,这些属性在序列化过程中会被忽略。
  • 无法处理循环引用(例如 obj.self = obj),会抛出错误。
  • 会丢失 Date 对象,会被转换成 ISO 字符串。
  • 会丢失 RegExp 对象,变成空对象 {}。
  • 会丢失 Map,Set,WeakMap,WeakSet。
const original = {
  a: 1,
  b: { c: 2 },
  d: new Date(),
  e: undefined,
  f: function() {}
};

const deepCopy = JSON.parse(JSON.stringify(original));

console.log(deepCopy);
// 输出:{ a: 1, b: { c: 2 }, d: "2023-10-25T..." } (date变成了字符串,undefined和function没了)
  1. 使用第三方库,常用的如lodash