JSON.parse不是万能药!手把手教你写一个深拷贝!

我用租房合同的比喻帮你彻底理解这个问题

一、生活化解释(租房合同版)

1. 浅拷贝 - 二房东转租

javascript 复制代码
// 原始租房合同
const originalContract = {
  address: "杭州西湖101室",
  items: ["空调", "沙发"], // 共用家具
  landlord: { name: "王阿姨" } // 共用房东信息
};

// 二房东做浅拷贝转租
const shallowCopy = { ...originalContract };

shallowCopy.address = "杭州西湖101室(隔断间)"; // ✅ 修改自己的地址
shallowCopy.items.push("洗衣机"); // ❌ 实际修改了原始合同的家具

现象 :原始合同的items数组被修改,所有租客共享同一份家具清单

2. 深拷贝 - 重新签订合同

javascript 复制代码
// 使用深拷贝生成新合同
function deepClone(obj) { /* 稍后实现 */ }

const newContract = deepClone(originalContract);
newContract.items.push("电视机"); // ✅ 只影响新合同

结果:新旧合同完全独立,互不影响

二、代码案例对比

浅拷贝实现方式

javascript 复制代码
// 案例对象
const school = {
  name: "浙江大学",
  departments: ["计算机系", "电子系"],
  info: { location: "北京" }
};

// 方法1:展开运算符
const shallowCopy1 = { ...school };

// 方法2:Object.assign
const shallowCopy2 = Object.assign({}, school);

// 测试修改
shallowCopy1.departments.push("数学系");
console.log(school.departments); // ["计算机系", "电子系", "数学系"]

三、手写深拷贝(详细注释版)

javascript 复制代码
function deepClone(target, map = new WeakMap()) {
  // 基本类型直接返回(终止递归的条件)
  if (typeof target !== 'object' || target === null) {
    return target;
  }

  // 处理循环引用:如果已经拷贝过该对象,直接返回存储的副本
  if (map.has(target)) {
    return map.get(target);
  }

  // 处理特殊对象类型
  const constructor = target.constructor;
  // Date类型:new Date(value)
  if (target instanceof Date) {
    return new Date(target);
  }
  // RegExp类型:new RegExp(source, flags)
  if (target instanceof RegExp) {
    return new RegExp(target.source, target.flags);
  }

  // 创建新容器(数组/对象)
  const clone = Array.isArray(target) ? [] : {};

  // 记录当前对象与拷贝对象的映射关系(解决循环引用)
  map.set(target, clone);

  // 递归拷贝所有属性
  for (let key in target) {
    // 只拷贝对象自身的属性(不拷贝原型链上的属性)
    if (target.hasOwnProperty(key)) {
      clone[key] = deepClone(target[key], map);
    }
  }

  return clone;
}

测试用例

javascript 复制代码
const original = {
  name: "测试对象",
  features: ["嵌套", { data: "结构" }],
  created: new Date(),
  regex: /abc/gi,
  circular: null // 准备设置循环引用
};

// 设置循环引用
original.circular = original;

// 执行深拷贝
const cloned = deepClone(original);

// 修改拷贝对象
cloned.features[1].data = "修改后的数据";

console.log(original.features[1].data); // 仍然是"结构"
console.log(cloned.circular === cloned); // true(保持循环引用结构)

四、核心区别分析

对比维度 浅拷贝 深拷贝
复制层级 只复制第一层属性 递归复制所有嵌套层级
内存关系 嵌套对象共享内存地址 所有层级都创建新内存空间
修改影响 修改嵌套属性会影响原对象 完全独立,互不影响
性能消耗 快(O(n)) 慢(O(n²))
适用场景 简单数据结构的快速复制 需要完全隔离的复杂对象
处理循环引用 无法处理 通过WeakMap记录映射关系
特殊对象处理 直接复制引用 需要特殊处理Date/RegExp等类型

五、开发中的经典应用场景

需要浅拷贝的场景

javascript 复制代码
// 场景:合并默认配置
const defaultConfig = { timeout: 3000, retry: 3 };
const userConfig = { timeout: 5000 };

// 正确做法:浅拷贝合并
const finalConfig = { ...defaultConfig, ...userConfig };
// { timeout: 5000, retry: 3 }

必须用深拷贝的场景

javascript 复制代码
// 场景:保存游戏进度
const currentGameState = {
  player: { level: 5, items: ["剑", "盾"] },
  enemies: [{ type: "orc" }, { type: "dragon" }]
};

// 正确做法:深拷贝存档
const savedGame = deepClone(currentGameState);

// 修改当前游戏状态不会影响存档
currentGameState.player.level = 6;
console.log(savedGame.player.level); // 仍然是5

六、如何选择拷贝方式?

  1. 优先考虑浅拷贝

    • 当数据没有嵌套结构时
    • 需要高性能处理的简单数据
    • 明确知道只需要修改第一层属性
  2. 必须使用深拷贝

    • 处理来自外部的数据(防止数据污染)
    • 需要持久化存储的状态
    • 在状态管理(如Redux)中修改嵌套状态
    • 处理包含多个嵌套层级的配置对象

建议:当不确定数据结构时,使用深拷贝更安全!但要注意对特殊类型(如函数、DOM元素)的处理需要额外逻辑。

相关推荐
恋猫de小郭13 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端