[Medium] 手写 cloneDeep (深拷贝)

2024年3月8日

💎 加入 E+ 成長計畫 與超過 500+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

题目描述

在 JavaScript 复制值时,当复制的是非原始型别 (primitive type) 的资料型别时,例如:物件(object)、阵列 (array) 等,会遇到浅拷贝 (shallow copy) 和深拷贝 (deep copy) 的差异。在面试时被很常会要你当场手写深拷贝,也就是手写 Lodash 常见的 cloneDeep

所谓的深拷贝是指在拷贝时,物件 A 与物件 B 不同,两者在原型链上仅是结构相同,但其属性实际的地址不同。在拷贝值时,有可能会遇到变数是多层的情境,例如是一个物件里还有物件,深拷贝的定义会是每一层的值都不会共享址 (reference)。

具体来说,以 lodash 这个套件提供的效用函式为例,有分成  clone  和  cloneDeep  两种不同效用函式,clone  只用于浅拷贝(第一层拷贝),但  cloneDeep  可用于深拷贝。下面的例子说明两者的区别:

// lodash 的浅拷贝 clone
var objects = [{ a: 1 }, { b: 2 }];
var shallow = _.clone(objects);
console.log(objects === shallow); // false
console.log(shallow[0] === objects[0]); // true

// lodash 的深拷贝 cloneDeep
var objects = [{ a: 1 }, { b: 2 }];
var deep = _.cloneDeep(objects);
console.log(objects === deep); // false
console.log(deep[0] === objects[0]); // false

本题解答

以下是本题的解答,详细解题思路可以在 E+ 成长计划看到。如果想练习更多题目,推荐可以到 GreatFrontEnd 上练习

解法一

let objA = {
  a: 1,
  b: { c: 3 },
};

function cloneDeep(item) {
  return JSON.parse(JSON.stringify(item));
}

let objB = deepCopy(objA);

console.log(objA === objB); // false
console.log(objA.b === objB.b); // false

解法二

let objA = {
  a: 1,
  b: { c: 3 },
};

let objB = structuredClone(objA);

console.log(objA === objB); // false
console.log(objA.b === objB.b); // false

解法三

function deepClone(obj, cache = new WeakMap()) {
  if (cache.has(obj)) {
    return cache.get(obj);
  }

  if (obj === null || typeof obj !== "object" || typeof value === "function") {
    return obj;
  }

  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);

  const result = Array.isArray(obj)
    ? []
    : Object.create(Object.getPrototypeOf(obj));

  cache.set(obj, result);

  for (const key of Reflect.ownKeys(obj)) {
    const value = obj[key];
    result[key] = deepClone(value, cache);
  }

  return result;
}
🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們