[1-5] 浅拷贝与深拷贝 · 实现方式对比与手写示例(含循环引用处理)

7 阅读3分钟

所属板块:1. 数据类型与内存机制

记录日期:2026-03-xx
更新:根据实际项目中拷贝需求补充

1. 拷贝的本质区别(复习栈/堆)

方式拷贝内容是否共享引用修改拷贝后原对象是否变化适用场景
直接赋值复制栈中值/地址指针是(引用类型共享)基本类型;临时引用传递
浅拷贝只拷贝第一层(地址)是(嵌套对象共享)简单对象/数组,不嵌套太深
深拷贝递归拷贝所有层需要完全独立副本的场景

核心:引用类型直接赋值或浅拷贝,只复制了“指针”,改副本会影响原对象。

2. 常见浅拷贝方式对比

方法优点缺点 / 注意点适用性
Object.assign(target, source)原生、简单只浅拷贝;target 会被修改;不拷贝 Symbol 属性合并对象
{...obj}(展开运算符)简洁、ES6+只浅拷贝第一层;不拷贝 Symbol、非可枚举属性快速克隆普通对象
Array.prototype.slice()数组专用只浅拷贝数组本身,不处理嵌套一维数组
Array.prototype.concat()数组合并同上数组合并

示例(浅拷贝的坑):

const obj = { a: 1, b: { c: 2 } };
const shallow = { ...obj };

shallow.a = 999;           // 不影响原 obj
shallow.b.c = 999;         // 原 obj.b.c 也变了 999
console.log(obj.b.c);      // 999

3. 常见深拷贝方式(及其局限)

  1. JSON.parse(JSON.stringify(obj))
    最简单,但有严重缺陷:

    • 丢失函数、Symbol、undefined、正则、Date、Map/Set 等
    • 无法处理循环引用(会报错)
    • 性能一般
    const obj = { a: 1, fn: () => {}, date: new Date(), reg: /abc/ };
    const copy = JSON.parse(JSON.stringify(obj));
    console.log(copy.fn);      // undefined
    console.log(copy.date);    // 变成字符串
    
  2. 结构化克隆 structuredClone(现代浏览器/Node 推荐)
    2022年后浏览器支持,Node 17+ 支持

    • 支持 Date、RegExp、Map、Set、Error 等
    • 支持循环引用(自动处理)
    • 不支持函数、DOM节点、非结构化数据
    const original = { a: { b: {} } };
    original.a.b.self = original.a.b;  // 循环引用
    
    const clone = structuredClone(original);
    clone.a.b.self.c = 999;
    console.log(original.a.b.self.c);  // undefined(不影响原对象)
    

    注意:目前(2026)兼容性已很好,但老项目/老浏览器仍需 polyfill 或 fallback。

4. 手写递归深拷贝(面试/项目常用方案)

目标:支持基本类型、普通对象、数组、循环引用(用 WeakMap 记录已拷贝对象)

function deepClone(obj, visited = new WeakMap()) {
  // 基本类型 / null 直接返回
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 处理 Date、RegExp 等特殊内置对象
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);

  // 循环引用:已拷贝过,直接返回之前的结果
  if (visited.has(obj)) {
    return visited.get(obj);
  }

  // 创建新对象(数组或普通对象)
  const clone = Array.isArray(obj) ? [] : {};

  // 记录已拷贝(防止循环引用)
  visited.set(obj, clone);

  // 递归拷贝属性
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      clone[key] = deepClone(obj[key], visited);
    }
  }

  return clone;
}

// 测试循环引用
const obj = { a: 1 };
obj.self = obj;

const copy = deepClone(obj);
copy.a = 999;
console.log(obj.a);         // 仍为 1
console.log(copy.self === copy);  // true(正确处理循环)

注意:

  • 用 WeakMap 而不是 Map:WeakMap 的 key 是弱引用,对象回收时自动清理,不会造成额外内存泄漏
  • 没处理 Symbol 属性(如需,可加 Object.getOwnPropertySymbols)
  • 没处理函数(函数通常不需要深拷贝,或用其他方式)

5. 小结 & 实际使用建议

  • 简单对象 → {...obj} 或 Object.assign
  • 需要支持 Date/Map/Set/循环引用 → 优先 structuredClone(现代环境)
  • 兼容性要求高 / 需自定义 → 手写 deepClone(用 WeakMap 防循环)
  • 项目中建议封装一个 clone 函数,根据环境 fallback:
    const clone = typeof structuredClone === 'function' 
      ? structuredClone 
      : deepClone;
    

第一板块(数据类型与内存机制)到此基本覆盖主要内容。

返回总目录:戳这里