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元素)的处理需要额外逻辑。

相关推荐
SuperYing7 分钟前
前端候选人突围指南:让面试官主动追着要简历的五大特质(个人总结版)
前端·面试
前端双越老师10 分钟前
我的编程经验与认知
前端
linweidong16 分钟前
前端Three.js面试题及参考答案
前端·javascript·vue.js·typescript·前端框架·three.js·前端面经
GISer_Jing25 分钟前
前端常问的宏观“大”问题详解(二)
linux·前端·ubuntu
Moment26 分钟前
前端性能指标 —— CLS
前端·javascript·面试
掘金安东尼1 小时前
上周前端发生哪些新鲜事儿? #407
前端·面试·github
小谭鸡米花1 小时前
ECharts各类炫酷图表/3D柱形图
前端·javascript·echarts·大屏端
郝晨妤1 小时前
【鸿蒙5.0】向用户申请麦克风授权
linux·服务器·前端·华为·harmonyos·鸿蒙
神秘代码行者1 小时前
使用 contenteditable 属性实现网页内容可编辑化
前端·html5
小鱼人爱编程1 小时前
Look My Eyes 最新IDEA快速搭建Java Web工程的两种方式
java·前端·后端