问题

1、引用类型的问题

image.png

  1. let obj1 = {a:123} // 赋值的是内存地址,假设是0x001
  2. let obj2 = obj1 // 两个变量,指向的都是同一个内存地址
  3. obj2.a = 111 // 因此修改的是同一个内存地址的对象
  4. console.log(obj1) // {a:111}

image.png

  1. // 源对象
  2. let obj1 = { a: 0 , b: { c: 0}};
  3. // 浅拷贝
  4. let obj2 = Object.assign({}, obj1);
  5. // 或者
  6. let obj3 = {...obj1}
  7. log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}
  8. obj1.a = 1; // 只拷贝了表面一层
  9. log(JSON.stringify(obj1));// { a: 1, b: { c: 0}}
  10. log(JSON.stringify(obj2));// { a: 0, b: { c: 0}}
  11. // 无法拷贝深层
  12. obj1.b.c = 111;
  13. JSON.stringify(obj1)// "{"a":0,"b":{"c":111}}"
  14. JSON.stringify(obj2)// "{"a":0,"b":{"c":111}}"

image.png

2、简单深拷贝的问题

image.png

  1. let obj1 = {a:123}
  2. let obj2 = JSON.parse(JSON.stringify(obj1)) // 简单的深拷贝,JSON.parse会根据JSON字符串创建一个新对象
  3. obj2.a = 111 // 修改的不是同一个对象
  4. console.log(obj1) // {a:123}

image.png image.png

自定义深拷贝

目标

image.png

基本实现

定义一个函数,传入一个对象,把这个对象拷贝成新的,然后返回出去。

1、遍历key

首先里面创建一个新的对象,遍历传入对象所有的key,然后赋值给新对象,返回出去。
此时只是浅拷贝,如果value里面是个对象,这样只是简单把对象内存地址复制过去而已,并没有拷贝。
image.png

2、递归调用

修改一下,遍历时调用自己就可以了,这样如果第二层是一个对象,还会调用自己创建一个newObject,那么所有层的对象也解决了。
image.png

3、判断基本数据类型

然后优化一下,如果传入的是基本数据类型(字符串,数值等),直接返回就可以了。
那么需要自己定义一个判断是否是对象的函数
image.png
image.png

4、判断数组

目前问题还有数组,数组传入后会判断是一个对象,然后创建一个对象{},相当于把数组变成了对象。
优化就是如果是对象,再进行判断,如果是数组就新建数组,如果不是就新建对象。
image.png

5、判断函数

目前问题如果值是函数,传入后,会变成对象{},因为函数是对象且不是数组,因此newObject会创建对象

只要再前面加一层判断就可以了,如果是函数直接就返回函数,因为函数虽然也是引用类型,但不存在说我改你函数里面的内容(函数就是为了复用),不像对象那样可以修改里面的部分值。
因此直接复制同一个引用就可以了。
image.png

6、判断Symbol

image.png
image.png
这里有两个问题:
1、Symbol 作为key的时候,没有成功拷贝过来(for循环会无视Symbol )
2、Symbol 作为value的时候,是拷贝过来了,但是用的是同一个 Symbol 内存地址。

针对问题2,多做一次判断就可以了,如果值是Symbol 就创建Symbol ,把描述符拿过来直接用。
image.png

针对问题1,用方法获取所有的Symbol 的key,然后遍历,遍历时赋值和递归调用本函数处理value就可以了。
image.png

7、判断 set / map

目前传入的set类型和map类型,都会当做对象来处理,且里面是空的

前面加个判断,这里判断条件不能用typeof,会认为是一个对象。

用instanceof判断,传入的值是不是Set对象的实例就可以了,是的话就创建新的Set,然后通过扩展运算符传入所有的元素。
image.png

map同理
image.png

但这里只是对set和map进行浅拷贝,如果这里也要深拷贝,一样需要对每个值遍历然后递归调用本函数即可。
但开发这样用的不多,常见的浅拷贝足够了。

8、循环引用

image.png
目前如果传入的对象,是这样的循环引用,程序直接就栈溢出崩溃了,原因是递归调用那里,不断的递归调用自己函数死循环

那么应该要在第一次传入这个属性的时候,判断传入的是不是自己就可以了,如果是自己就直接返回出去。

怎么判断,这里可以用map判断

先创建一个Map
image.png

然后传入源对象obj作为key,
第一次执行深拷贝函数时,源对象就是源对象obj;
第二次递归执行时,源对象是obj.inner,会进入判断,用has判断 obj 是不是等于 obj.inner,如果是就直接返回出去。
image.png

但是这里有个问题,如果创建的这个Map:
1、是在深拷贝函数外创建的,那么执行深拷贝函数的set方法时,就会给他添加一个键值对,其他地方调用同样会添加,这里就会越来越大。
2、如果这个是在深拷贝函数内创建的,那么第一次执行和第二次执行就是两个新的map,判断不到同一个map,因此就没办法判断obj 是不是等于 obj.inner
image.png

这里通过把map放入深拷贝参数可以解决这个问题。
image.png
这样第一次调用深拷贝时,不传入map参数,就会创建一个新的。
当第二次递归调用时,把第一次创建的那个map传进来第二次调用里面,这样就是同一个map
image.png

最后再改一下,改成弱引用map,这样函数执行完后,map会自行销毁,而普通的Map会因为对象的依赖关系导致不销毁
image.png