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

相关推荐
yuanmenglxb20043 分钟前
微信小程序核心技术栈
前端·javascript·vue.js·笔记·微信小程序·小程序
爱编程的鱼4 分钟前
如何让 HTML 文件嵌入另一个 HTML 文件:详解与实践
前端·html
_09278 分钟前
Vue 2 与 Vue 3 的核心区别及 Vue 3 新特性详解
前端
David凉宸9 分钟前
一文带你使用Vue完成移动端(apk)项目
前端
纪元A梦19 分钟前
华为OD机试真题——绘图机器(2025A卷:100分)Java/python/JavaScript/C++/C/GO最佳实现
java·javascript·c++·python·华为od·go·华为od机试题
会飞的鱼先生21 分钟前
Vue3的内置组件 -实现过渡动画 TransitionGroup
前端·javascript·vue.js·vue
晓得迷路了22 分钟前
10 分钟开发一个 Chrome 插件?Trae 让你轻松实现!
前端·javascript·trae
秋天的一阵风27 分钟前
Vue3探秘系列— 路由:vue-router的实现原理(十六-上)
前端·vue.js·面试
秋天的一阵风28 分钟前
Vue3探秘系列— 路由:vue-router的实现原理(十六-下)
前端·vue.js·面试
海底火旺1 小时前
JavaScript中的Object方法完全指南:从基础到高级应用
前端·javascript·面试