手写深拷贝

95 阅读3分钟

一、深拷贝 VS 浅拷贝

  1. 浅拷贝:如果拷贝的属性是引用类型的话,原始对象和副本对象共享内存。
  2. 深拷贝:如果拷贝的属性是引用类型的话,原始对象和副本对象不共享内存,副本会创建一个新的内存地址

简单理解:浅和深的相对于拷贝层级来划分的,如果只拷贝外层基本数据类型和引用类型的内存地址,不会递归复制引用类型属性所引用的对象,就叫做浅拷贝;如果递归复制引用类型属性所引用的对象,包括嵌套的对象和属性,并创建独立的对象副本,则叫做深拷贝。

二、实现深拷贝的方法及注意事项

2.1 JSON.parse 会生成一个新对象(无法处理函数、Symbol;如果有循环引用也会报错。)

const obj4 = JSON.parse(JSON.stringify(info))

2.2 自己手写实现

function deepCopy(originValue) {
      // 0.如果值是Symbol的类型
      if (typeof originValue === "symbol") {
        return Symbol(originValue.description) //由于 Symbol 的唯一性,即使描述符相同,创建的 Symbol 值也是不相等的。
      }

      // 1.如果是原始类型, 直接返回
      if (!isObject(originValue)) {
        return originValue
      }

      // 2.如果是set类型,for of 遍历item,且对item进行深拷贝
      if (originValue instanceof Set) {
        const newSet = new Set()
        for(const setItem of originValue) {
          newSet.add(deepCopy(setItem))
        }
      }
     

      // 3.如果是函数function类型, 不需要进行深拷贝
      if (typeof originValue === "function") {
        return originValue
      }

      // 2.如果是数组类型,创建数组;对象类型, 创建对象
      const newObj = Array.isArray(originValue) ? []: {}
      // 遍历普通的key
      for (const key in originValue) {
        newObj[key] = deepCopy(originValue[key]);
      }
      // 单独遍历symbol
      const symbolKeys = Object.getOwnPropertySymbols(originValue)
      for (const symbolKey of symbolKeys) {
        newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey])
      }

      return newObj
    }

三、追问问题:

1问:

为什么引用类型的属性值是函数,则不需要深拷贝,直接返回原来的引用地址就行了?

1答:

  1. 函数比较特殊,函数是用来执行的,不是用来保存数据的。也就是说,对于保存数据的类型,如果不深拷贝,只要修改一个对象,另一个也会被修改,无法保持独立;但是对于函数而言是不会有修改的操作的,自然也没有深拷贝的必要了。
  2. 函数的定义在内存中只有一个实例,无论我们创建多少个对该函数的引用,它们都指向同一个函数定义所在的内存地址,每调用一次函数,都会有独立的作用域和上下文。因此,如果每深拷贝一次,创建更多对函数的引用,只会浪费内存。

2问:

如何解决循环引用问题?

2答:

用weakMap来跟踪已经拷贝过的对象,传参map = new WeakMap()。

 function deepCopy(originValue, map = new WeakMap()) {
      // const map = new WeakMap()

      // 4.如果是对象类型, 才需要创建对象
      if (map.get(originValue)) {
        return map.get(originValue)
      }
      const newObj = Array.isArray(originValue) ? []: {}
      map.set(originValue, newObj)
      // 遍历普通的key
      for (const key in originValue) {
        newObj[key] = deepCopy(originValue[key], map);
      }

      return newObj
    }
-------------------------------------------以下是测试代码--------------------
    const info = {
      name: "abs",
      age: 12,
      classmates: {
        name: "xiaoliu",
        hobby: {
          name: "篮球",
          detail: "打篮球"
        }
      },
      // self: info
    }
    info.self = info //出现了循环引用的问题

    let newObj = deepCopy(info)
    console.log(newObj)
    console.log(newObj.self === newObj)