为什么要学习深拷贝?

在介绍深拷贝之前,我们首先要知道为什么要学习深拷贝,不仅仅是因为这是一道面试常考的问题,还因为在实际的开发中,我们经常会遇到需要进行深拷贝的场景,因此深拷贝这个问题是我们必须要掌握的,深拷贝其中涉及到的知识点还是很多的,通过这个知识点的学习可以帮助我们迅速拓展巩固其他相关的知识点。

基本数据类型和引用数据类型

在介绍深拷贝之前,我们首先要明白基本数据类型和引用数据类型的概念和区别。

基本数据类型没有子类型,不能再进行拆分了,但是复杂数据类型还有子类型。数据类型的不同,造成数据在内存中的存储方式的不同,简单数据类型都是存储在栈中,存储的是一个值,如果是复杂数据类型,存储在堆中,存储的是一个地址,正是因为存储方式的不同,导致了我们在拷贝对象的时候,有时候需要浅拷贝,有时候需要深拷贝。

基本数据类型

典型的基本数据类型有以下几种:

  • undefined
  • number
  • boolean
  • bigint
  • symbol
  • null
  • string

引用数据类型

典型的引用数据类型主要有以下几种:

  • Object
  • Array
  • Map
  • Set
  • Date
  • Regexp
  • Function

深拷贝和浅拷贝的区别

对浅拷贝来说:如果属性是基本类型,拷贝的就是对应的值,如果属性是引用类型,拷贝就是对应的内存地址,所以此时如果修改新拷贝对象中的引用类型的属性会影响到原对象中的属性。

对深拷贝来说:如果属性是基本类型,拷贝的是对应的值,如果是引用类型,会从堆内存中开辟一个新的空间存在新对象,修改新对象不会对原来的对象产生影响。

如何实现深拷贝

方式一:通过JSON.parse(JSON.stringify(obj))

下面介绍一个通过JSON.parse(JSON.stringify(obj))实现深拷贝的例子

let obj = {
  id:666,
  info:{
    name:"张三",
    age:24
  }
}
let obj2 = JSON.parse(JSON.stringify(obj))// 复杂数据类型也可以使用JSON.parse(JSON.stringify(obj))
obj2.info.age = 100;
obj.info.age  //24
obj2.info.age  // 100

需要注意的是JSON.parse这种方法并不是万能的,因为支持的数据类型是有限的,比如只支持object、array、string、number、boolean、null等,不支持undefined、function、正则、Date、环形obj、map、set等拷贝。

方式二:递归拷贝

所谓的递归拷贝就是我们通过递归的方式将无法直接拷贝的引用数据类型进行分贝拷贝。主要是考虑Array、Function、RegExp、Date、Map、Set等数据类型,同时需要考虑到循环引用并过滤掉原型身上的属性。

function deepClone(target, cache = new Map()) {
  if (cache.get(target)) {
    return cache.get(target)
  }
  if (target instanceof Object) {
    let dist;
    if (target instanceof Array) {
      // 拷贝数组
      dist = [];
    } else if (target instanceof Function) {
      // 拷贝函数
      dist = function () {
        return target.call(this, ...arguments);
      };
    } else if (target instanceof RegExp) {
      // 拷贝正则表达式
      dist = new RegExp(target.source, target.flags);
    } else if (target instanceof Date) {
      // 拷贝日期函数
      dist = new Date(target);
    } else if (target instanceof Map) {
      // 拷贝Map
      dist = new Map(target);
    } else if (target instanceof Set) {
      // 拷贝Set
      dist = new Set(target);
    } else {
      // 拷贝普通对象
      dist = {};
    }
    // 将属性和拷贝后的值作为一个map
    cache.set(target, dist);
    for (let key in target) {
      // 过滤掉原型身上的属性
      if (target.hasOwnProperty(key)) {
        dist[key] = deepClone(target[key], cache);
      }
    }
    return dist;
  } else {
    return target;
  }
}

总结

深拷贝的实现本质就是需要考虑不同数据类型该如何实现深拷贝,根据不同类型的特点来分别解决问题。