对象的拷贝

深浅拷贝

1
2
3
4
5
6
let a = {
age: 1,
};
let b = a;
a.age = 2;
console.log(b.age); // 2

从上述例子中我们可以发现,如果给一个变量赋值一个对象,那么两者的值会是同一个引用,其中一方改变,另一方也会相应改变。
通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个问题。

浅拷贝
首先可以通过 Object.assign 来解决这个问题。

1
2
3
4
5
6
let a = {
age: 1,
};
let b = Object.assign({}, a);
a.age = 2;
console.log(b.age); // 1

当然我们也可以通过展开运算符(…)来解决

1
2
3
4
5
6
let a = {
age: 1,
};
let b = { ...a };
a.age = 2;
console.log(b.age); // 1

通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就需要使用到深拷贝了

1
2
3
4
5
6
7
8
9
let a = {
age: 1,
jobs: {
first: "FE",
},
};
let b = { ...a };
a.jobs.first = "native";
console.log(b.jobs.first); // native

浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到刚开始的话题了,两者享有相同的引用。要解决这个问题,我们需要引入深拷贝。

深拷贝

这个问题通常可以通过 JSON.parse(JSON.stringify(object)) 来解决。

1
2
3
4
5
6
7
8
9
let a = {
age: 1,
jobs: {
first: "FE",
},
};
let b = JSON.parse(JSON.stringify(a));
a.jobs.first = "native";
console.log(b.jobs.first); // FE

但是该方法也是有局限性的:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let obj = {
a: 1,
b: {
c: 2,
d: 3,
},
};
obj.c = obj.b;
obj.e = obj.a;
obj.b.c = obj.c;
obj.b.d = obj.b;
obj.b.e = obj.b.c;
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);

如果你有这么一个循环引用对象,你会发现你不能通过该方法深拷贝。
在遇到函数、 undefined 或者 symbol 的时候,该对象也不能正常的序列化

1
2
3
4
5
6
7
8
let a = {
age: undefined,
sex: Symbol("male"),
jobs: function () {},
name: "lyb",
};
let b = JSON.parse(JSON.stringify(a));
console.log(b); // {name: "lyb"}

你会发现在上述情况中,该方法会忽略掉函数和 undefined 。
但是在通常情况下,复杂数据都是可以序列化的,所以这个函数可以解决大部分问题,并且该函数是内置函数中处理深拷贝性能最快的。当然如果你的数据中含有以上三种情况下,可以使用 lodash 的深拷贝函数

总结一下:

浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响

  1. Object.assign
  1. 展开运算符(…)

深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响

  1. JSON.parse(JSON.stringify(obj)): 性能最快
    具有循环引用的对象时,报错
    当值为函数、undefined、或 symbol 时,无法拷贝
  1. 递归进行逐一赋值