45行代码,教你实现一个属于自己的深拷贝工具函数(deepClone)

124 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


45行代码,教你实现一个属于自己的深拷贝工具函数(deepClone)


前言

近日翻开自己封装的工具类函数时,突然发现了自己以前封装的深拷贝工具函数,写过js的小伙伴应该都理解,关于引用类型的那些事,借此来记录和分分析一下当时封装函数的一些思路。


深拷贝与浅拷贝的区别

只针对引用类型考虑,像一些基本数据类型(number,String等)就没有浅拷贝这一说法。

1、js原生的赋值只是将对象指向的地址赋值给了另一个对象,故此只要其中一个对象修改了另一个对象也会随之变化。

2、有一种说法浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响,也有一种说法是引用赋值类型就是浅拷贝,但在我的理解中,只要拷贝对象与源对象能够互相影响,那就是浅拷贝。

3、深拷贝要做到的就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。


关于JSON.stringify的那些事


简单拷贝使用JSON.stringify-JSON.parse倒是没问题,在清楚接口回传回来的数据结构时使用是没问题的,同时也还算方便,但是一旦碰到以下几种情况,请千万千万不要使用JSON.stringify。

1、当对象中有时间类型的元素时候。

2、当对象中有undefined类型或function类型的数据时。

3、当对象中有NaN、Infinity和-Infinity这三种值的时候。

4、当对象循环引用的时候。

5、具有不可枚举的属性值时。

具体为什么咱就不具体展开说了,由于篇幅限制,有兴趣小伙伴们可以翻阅相关的JSON.stringify文章讲解。


思路分析

  1. 对象分类
  2. 递归
  3. 缓存解决循环引用的问题

开工,上代码


function deepClone(obj) {
  let cache = [] // 创建一个缓存对象
  function clone(obj) {
    if (obj instanceof Object) {
      let cachedDist = findCache(obj)
      if (cachedDist) {
        return cachedDist // 如果找到缓存,证明存在循环引用或属性引用了相同对象,直接返回引用就可以了
      } else {
        let dist
        if (obj instanceof Object) { 
          if (obj instanceof Array) { //  不同类型的对象对应不同的处理方法
              dist = []
            } else if (obj instanceof Function) {
              dist = function () {
                return obj.call(this, ...arguments)
              }
            } else if (obj instanceof RegExp) {
              dist = new RegExp(obj.obj, obj.flags)
            } else if (obj instanceof Date) {
              dist = new Date(obj)
            } else {
              dist = {}
            }
          cache.push([obj, dist]) // 把源对象和新对象放进缓存列表
          for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
              // 只复制不在原型上的属性
              dist[key] = clone(obj[key]) // 递归
            }
          }
          return dist
        }
      }
    }
    return obj
  }
​
  function findCache(obj) {
    for (let i = 0; i < cache.length; i++) {
      if (cache[i][0] === obj) {
        return cache[i][1] 
      }
    }
  }
  return clone(obj)
}

结尾

当然这样处理深拷贝的方式不是最完美的,如果想再进一步解决一些问题的话,推荐可以去看Lodash.cloneDeep() ,当然,直接使用loadsh也是没有问题的。

最后感谢各位看官能够看到这里~