手撕深拷贝?这一篇就够了!

467 阅读4分钟

前言:

在之前的文章中提到过关于深拷贝和浅拷贝的概念,给各位详细介绍了一下二者的区别。但是在关于深拷贝的部分,通过JSON.parse和JSON.stringify是无法对null,symbol以及bigint等类型的值进行拷贝甚至报错的。正常情况下,会使用Lodash库去实现深拷贝,但是很显然,在各大卷王的激情贡献下,面试官们已经完全不满足于这个回答了

正文:

递归深拷贝:

在手写前还是要简单说明一下,深拷贝或浅拷贝这一概念仅针对于引用类型的值(比如object,array),而基础类型则无一例外全部都是深拷贝,所以在手写的深拷贝函数的时候,最先需要的就是判断数据类型,是基础类型则直接返回否则再进行深拷贝操作

function deepCopy(obj) {
  if (obj === null) return null; // 处理null值
  if (!(obj instanceof Object)) return obj; // 基本数据类型直接返回
}

随后,判断拷贝的值是数组还是对象并设置返回值

  let copyObj = Array.isArray(obj) ? [] : {}

   接下来就是通过for-in方法去遍历拷贝的值,并且对每个值都要判断是否是自身属性。需要注意一点的是,每个元素都有可能是引用类型,切记不可直接赋值,否则还是浅拷贝。所以应该通过循环调用,也就是递归去不断地往深处寻找,直到找到原始类型的值,就是递归出口

for (key in obj) {
  if (obj[key] instanceof Object) {
    copyObj[key] = deepCopy(obj[key]);
  } else {
    copyObj[key] = obj[key];
  }
}

最后,将拷贝后的值返回

function deepCopy(obj) {
  if (obj === null) return null; // 处理null值
  if (!(obj instanceof Object)) return obj; // 基本数据类型直接返回   \
  let copyObj = Array.isArray(obj) ? [] : {};
  for (key in obj) {
    if (obj[key] instanceof Object) {
      copyObj[key] = deepCopy(obj[key]);
    } else {
      copyObj[key] = obj[key];
    }
  }
  return copyObj;
}

非递归深拷贝:

当然,如果对象叠加层数非常多,而且还无法对递归进行尾调用处理以至于通过递归深拷贝会造成爆栈(真是多到离谱),那么我们也可以利用weakMap数据类型,通过非递归的方法去进行深拷贝

  1. 首先,还是一样的进行类型判断,然后根据拷贝值的类型去设置返回体
function deepCopyII(obj, hash = new WeakMap()) {
  if (obj === null) return null; // 处理null值
  if (typeof obj !== "object") return obj; // 基本数据类型直接返回
   // 检查是否已经拷贝过
  if (hash.has(obj)) return hash.get(obj);
  let cloneObj = Array.isArray(obj) ? [] : {};
}

  1. 随后,设置一个队列,把要拷贝的值和返回值塞进一个数组里面,然后再把数组放进队列
// 使用队列来存储需要拷贝的对象属性 
let queue = [[obj, cloneObj]];

当队列内部还有值的时候,首先解构出原数据和拷贝数据

while (queue.length > 0) {
  let [original, copy] = queue.shift();
}

 

  1. 然后通过for-in方法去遍历原数据,这里还是一样,要判断是否自身属性以及原数据是否依旧是引用类型。如果是原始类型,则将原数据的值直接赋值给返回值的对应键或下标。否则就将这个引用类型的值和一个新的对象(数组)塞进新数组并重新放回队列。这样就能保证在不使用递归的前提下能一层层找到原数据的最底层
for (let key in original) {
  if (original.hasOwnProperty(key)) {
    // 确保是自身属性
    if (typeof original[key] === "object" && original[key] !== null) {
      // 如果属性值也是对象,则递归拷贝
      if (!hash.has(original[key])) {
        // 创建新的拷贝
        let newObj = Array.isArray(original[key]) ? [] : {};
        hash.set(original[key], newObj);
        queue.push([original[key], newObj]);
      } // 将原始对象的属性拷贝到新对象
      copy[key] = hash.get(original[key]);
    } else {
      // 如果不是对象,直接赋值
      copy[key] = original[key];
    }
  }
}

最后,返回拷贝的值

function deepCopyII(obj, hash = new WeakMap()) {
  if (obj === null) return null; // 处理null值
  if (typeof obj !== "object") return obj; // 基本数据类型直接返回 // 检查是否已经拷贝过
  if (hash.has(obj)) return hash.get(obj);
  let cloneObj;
  if (Array.isArray(obj)) {
    cloneObj = [];
  } else {
    cloneObj = {};
  }
  // 将原始对象和它的拷贝关联起来
  hash.set(obj, cloneObj); // 使用队列来存储需要拷贝的对象属性
  let queue = [[obj, cloneObj]];
  while (queue.length > 0) {
    let [original, copy] = queue.shift();
    for (let key in original) {
      if (original.hasOwnProperty(key)) {
        // 确保是自身属性
        if (typeof original[key] === "object" && original[key] !== null) {
          // 如果属性值也是对象,则递归拷贝
          if (!hash.has(original[key])) {
            // 创建新的拷贝
            let newObj = Array.isArray(original[key]) ? [] : {};
            hash.set(original[key], newObj);
            queue.push([original[key], newObj]);
          } // 将原始对象的属性拷贝到新对象
          copy[key] = hash.get(original[key]);
        } else {
          // 如果不是对象,直接赋值
          copy[key] = original[key];
        }
      }
    }
  }
  return cloneObj;
}

总结:

通过这两种方法,我们不仅能够有效地实现深拷贝,还能根据不同的需求和场景选择最适合的实现方式。无论是递归还是非递归深拷贝,都能够确保原始对象和拷贝对象之间是完全独立的,从而避免了修改一个对象时影响另一个对象的问题。最后祝各位看官老爷0 waring(s),0 error(s)。