深浅拷贝

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

从上述例子中我们可以发现,如果给一个变量赋值一个对象,那么两者的值会是同一个引用,其中一方改变,另一方也会相应改变。

通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个问题。

浅拷贝

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

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

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

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

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

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

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

深拷贝

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

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

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

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

如果你有这么一个循环引用对象,你会发现你不能通过该方法深拷贝

深浅拷贝 - 图1

在遇到函数、 undefined 或者 symbol 的时候,该对象也不能正常的序列化

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

你会发现在上述情况中,该方法会忽略掉函数和 undefined

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

如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel

  1. function structuralClone(obj) {
  2. return new Promise(resolve => {
  3. const {port1, port2} = new MessageChannel();
  4. port2.onmessage = ev => resolve(ev.data);
  5. port1.postMessage(obj);
  6. });
  7. }
  8. var obj = {a: 1, b: {
  9. c: b
  10. }}
  11. // 注意该方法是异步的
  12. // 可以处理 undefined 和循环引用对象
  13. (async () => {
  14. const clone = await structuralClone(obj)
  15. })()