6、✅ 手写深拷贝(deepClone),支持循环引用

108 阅读2分钟

🎯 一、为什么要掌握深拷贝?

  • JS 中对象赋值是引用赋值,不是值复制
  • JSON.parse(JSON.stringify()) 有诸多缺陷(如丢失函数、正则、undefined、循环引用会报错)
  • 项目中常需要完整复制一份对象结构,避免原始数据被意外修改
  • 面试中常考实现 + 处理边界

📌 二、深拷贝 vs 浅拷贝 区别

拷贝类型拷贝层级会复制引用吗工具
浅拷贝只拷贝第一层Object.assign、展开运算符
深拷贝全部层级递归否(新地址)自定义函数、structuredClone(现代浏览器)

🧠 三、深拷贝核心难点

  1. 递归嵌套结构
  2. 处理循环引用
  3. 正确处理各种类型(Date、RegExp、Set、Map、Function、Symbol、BigInt、null、undefined)

✍️ 四、基础版实现(不支持循环引用)

function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;

  const result = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key]);
    }
  }
  return result;
}

🔁 五、进阶版:支持循环引用 & 多类型处理

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;

  // 如果对象已被拷贝过,直接返回缓存值(处理循环引用)
  if (hash.has(obj)) return hash.get(obj);

  // 特殊对象类型处理
  const type = Object.prototype.toString.call(obj);

  // 日期
  if (type === '[object Date]') return new Date(obj);
  // 正则
  if (type === '[object RegExp]') return new RegExp(obj.source, obj.flags);
  // Map
  if (type === '[object Map]') {
    const map = new Map();
    hash.set(obj, map);
    obj.forEach((v, k) => map.set(deepClone(k, hash), deepClone(v, hash)));
    return map;
  }
  // Set
  if (type === '[object Set]') {
    const set = new Set();
    hash.set(obj, set);
    obj.forEach(v => set.add(deepClone(v, hash)));
    return set;
  }

  // 普通对象/数组
  const result = Array.isArray(obj) ? [] : {};
  hash.set(obj, result);

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key], hash);
    }
  }

  return result;
}

✅ 六、测试用例

6.1 基础对象

const obj = { name: 'Mark', age: 18, skills: ['JS', 'Vue'] };
const clone = deepClone(obj);
console.log(clone); // 完整拷贝

6.2 循环引用

const obj = { name: 'A' };
obj.self = obj;
const clone = deepClone(obj);
console.log(clone.self === clone); // true,说明循环引用成功处理

6.3 包含 Map/Set/Date/RegExp

const obj = {
  date: new Date(),
  reg: /abc/gi,
  map: new Map([['a', 1]]),
  set: new Set([1, 2])
};
const clone = deepClone(obj);
console.log(clone);

❗ 七、面试常见加分点

问题解法
如何处理循环引用?使用 WeakMap
为什么不用 JSON 序列化?会丢失函数、undefined、循环引用报错
如何处理特殊对象?判断 Object.prototype.toString.call(obj) 的类型

📘 八、现代浏览器方案

const newObj = structuredClone(obj); // 现代浏览器支持,内建处理循环引用、Map、Set 等

缺点:

  • 不支持 function、DOM 节点等
  • 兼容性较差(IE、老版本 Safari)