深拷贝的实现方法和注意事项

什么叫深拷贝?

深拷贝:深拷贝是指,拷贝对象的具体内容,二内存地址是自主分配的,拷贝结束之后俩个对象虽然存的值是一样的,但是内存地址不一样,俩个对象页互相不影响,互不干涉

实现深拷贝的几种方法?

JSON深拷贝

JSON.parse(JSON.stringify)为什么可以实现深拷贝?

使用JSON.stringify将对象序列化,转成字符串后,存储在硬盘上,在通过JSON.parse()反序列化,将字符串转成对象.它有一个存储操作就是深拷贝.

    let userA = {
            name: "张三",
            age: 24,
            num: undefined,
            sex: () => {
                console.log("我是函数")
            },
            time: new Date()
        }
        let userB = JSON.parse(JSON.stringify(userA));
        userB.age = 30;
        console.log("对象userA", userA);
        console.log("对象userB", userB);

最终结果:
在这里插入图片描述
以上的方式:我们可以仔细看一下,它在user A中有5个对象,但是B中只有3个,所以我们可以看出它在拷贝的过程中把某些对象丢了.我们再仔细看一下,它在拷贝过程中,将undefined和函数弄丢了,将time做了一个转义.但是这不是我们想要的,所以它存在以下缺点:
在这里插入图片描述

  • 忽略undefined和symbol
  • 无法拷贝function
  • 无法拷贝循环引用对象(比如对象属性的值就是它自己)
  • 对Date、RegExp等一些内置函数的处理会出现问题,比如时间不再是对象格式。
递归实现

递归实现的原理很简单,就是循环遍历我们的对象,如果遇到属性是对象的,再一次递归循环遍历即可。

let userA = {
  name: "张三",
  age: 20,
  sex: () => {
    console.log("小猪课堂");
  },
  arr: ["1", "231", "342242"],
  obj: {
    like: "打游戏"
  },
  likesFood: new Set(["fish", "banana"]),
  date: new Date()
};

function deepClone(obj) {
  // 如果传入的类型不对,则不做处理
  if (typeof obj !== "object" || obj === null) {
    return;
  }
  let newObj = {}; // 新对象
  const dataKeys = Object.keys(obj); // 获取原对象所有键值
  dataKeys.forEach((value) => {
    const currentValue = obj[value];
    // 基础类型直接赋值
    if (typeof currentValue !== "object" || currentValue === null) {
      newObj[value] = currentValue;
    } else if (Array.isArray(currentValue)) {
      // 实现数组的深拷贝
      newObj[value] = [...currentValue];
    } else if (currentValue instanceof Set) {
      // 实现set数据的深拷贝
      newObj[value] = new Set([...currentValue]);
    } else if (currentValue instanceof Map) {
      // 实现map数据的深拷贝
      newObj[value] = new Map([...currentValue]);
    } else if (currentValue instanceof Date) {
      // 日期类型深拷贝
      newObj[value] = new Date(currentValue.valueOf())
    } else {
      // 普通对象则递归赋值
      newObj[value] = deepClone(currentValue);
    }
  });
  return newObj;
}
let userB = deepClone(userA);
userB.obj.like = "睡觉"
console.log("userA",userA)
console.log("userB",userB)

在这里插入图片描述
虽然递归能够满足我们日常中大部分列子,很多小伙伴用的就是这种方法,但是它也有问题所在,比如循环引用的问题,对象的属性值就是该对象。
如果将上述代码改成:

let userA = {
  name: "张三",
  age: 20,
  sex: () => {
    console.log("小猪课堂");
  },
  arr: ["1", "231", "342242"],
  obj: {
    like: "打游戏"
  },
  likesFood: new Set(["fish", "banana"]),
  date: new Date()
};
userA.name = userA;

我们就加了一行代码,将name的值改为了userA,这就产生了循环引用/此时我们在使用该递归进行深拷贝,直接报栈溢出了,进入了死循环!
在这里插入图片描述
所以要接着进行升级:

let userA = {
  name: "张三",
  age: 20,
  sex: () => {
    console.log("小猪课堂");
  },
  arr: ["1", "231", "342242"],
  obj: {
    like: "打游戏"
  },
  likesFood: new Set(["fish", "banana"]),
  date: new Date()
};
userA.name = userA;

function deepClone(obj, hashMap = new WeakMap()) {
  // 如果传入的类型不对,则不做处理
  if (typeof obj !== "object" || obj === null) {
    return;
  }
  // 查缓存字典中是否已有需要克隆的对象,有的话直接返回同一个对象(同一个引用,不用递归无限创建进而导致栈溢出了)
  if (hashMap.has(obj)) {
    return hashMap.get(obj);
  }
  let newObj = {}; // 新对象
  const dataKeys = Object.keys(obj); // 获取原对象所有键值
  dataKeys.forEach((value) => {
    const currentValue = obj[value];
    // 基础类型直接赋值
    if (typeof currentValue !== "object" || currentValue === null) {
      newObj[value] = currentValue;
    } else if (Array.isArray(currentValue)) {
      // 实现数组的深拷贝
      newObj[value] = [...currentValue];
    } else if (currentValue instanceof Set) {
      // 实现set数据的深拷贝
      newObj[value] = new Set([...currentValue]);
    } else if (currentValue instanceof Map) {
      // 实现map数据的深拷贝
      newObj[value] = new Map([...currentValue]);
    } else if (currentValue instanceof Date) {
      // 日期类型深拷贝
      newObj[value] = new Date(currentValue.valueOf())
    } else {
      hashMap.set(obj, newObj); // 哈希表缓存新值
      // 普通对象则递归赋值
      newObj[value] = deepClone(currentValue,hashMap);
    }

  });
  return newObj;
}
let userB = deepClone(userA);
userB.obj.like = "睡觉"
console.log("userA", userA)
console.log("userB", userB)

上段代码解决了我们循环引用的问题,主要利用了WeakMap()这个方法,它的作用其实主要就是创建一个缓存表,我们深拷贝时,会先判断该属性值是否在缓存表当中,存在则说明是循环引用。

Object.assign()

Object.assign()是一种比较简单的深拷贝方法,其实说它是深拷贝,一点都不严谨,因为它只会拷贝第一层,也就是当对象内嵌套有对象的时候,被嵌套的对象进行的还是浅拷贝。

let userA = {
  name: "张三",
  age: 24
}
let userB = Object.assign({},userA);
userB.age = 30;
console.log("userA",userA);
console.log("userB",userB);

利用Lodash库(完美)

这个库相信很多小伙伴都用过,简直是前端开发利器。lodash是一套工具库,内部封装了很多字符串、数组、对象等常见数据类型的处理函数。

它提供了给我们进行深拷贝的方法,各种情况他都考虑到了,如果不在乎引入第三方库的话,强烈建议使用此方法。
直接调用提供给我们的_.cloneDeep方法即可。

_.cloneDeep(obj1);

利用Jquery

Jquery提供了深拷贝的方法给我们,但是目前这个大前端环境下,很多人不在使用jquery,所以这儿就简单介绍一下。

$.extend(true,{},obj)

直接调用extend方法即可。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值