前端必备技能:彻底搞懂JavaScript深浅拷贝,告别数据共享的坑!

❤ 写在前面

如果觉得对你有帮助的话,点个小❤❤ 吧,你的支持是对我最大的鼓励~

个人独立开发wx小程序,感谢支持!


开篇故事:为什么我的数据"打架"了?

想象一下这个场景:你在开发一个购物车功能,复制了一个商品对象准备修改数量,结果发现原始商品的数据也变了!这种"灵异事件"让很多前端开发者头疼不已。

javascript 复制代码
let originalProduct = {
  name: "JavaScript高级程序设计",
  price: 99,
  details: {
    publisher: "人民邮电出版社",
    pages: 728
  }
};

// 你以为的"复制"
let copiedProduct = originalProduct;
copiedProduct.price = 79; // 修改副本的价格

console.log(originalProduct.price); // 79?!原对象也被修改了!

这就是我们今天要解决的"深浅拷贝"问题!下面,让我们一步步解开这个谜团。

内存模型:理解深浅拷贝的基础

在深入之前,我们先看看JavaScript中数据是如何存储的:

css 复制代码
┌─────────────┐      ┌───────────────┐
│  栈内存     │      │   堆内存       │
│ (Stack)     │      │   (Heap)      │
├─────────────┤      ├───────────────┤
│ 基本类型    │      │               │
│ 变量名|值   │      │  引用类型     │
│ a -> 10     │      │  的对象数据   │
│ b -> true   │      │  {name: "xxx"}│
│             │      │  [1,2,3]      │
│ obj1 -> ↗═══╪══════> {x: 1, y: 2}  │
│ obj2 -> ↗═══╪══════> {name: "test"}│
└─────────────┘      └───────────────┘

基本类型(Number, String, Boolean等)直接存储在栈内存中,而引用类型(Object, Array等)在栈中只存储地址指针,真正的数据在堆内存中。

深浅拷贝对比流程图

flowchart TD A[原始对象] --> B{选择拷贝方式} B --> C[浅拷贝] B --> D[深拷贝] C --> E[只复制第一层属性] E --> F[嵌套对象仍共享内存] F --> G[修改嵌套属性会影响原对象] D --> H[递归复制所有层级] H --> I[完全独立的新对象] I --> J[新旧对象互不影响] G --> K[适用场景:简单数据结构] J --> L[适用场景:复杂嵌套对象]

浅拷贝:只挖第一层

浅拷贝就像只复制房子的钥匙,不复制房子里的家具。

常见的浅拷贝方法

方法1:展开运算符(最常用)

javascript 复制代码
let shallowCopy = { ...originalObject };
let shallowCopyArray = [...originalArray];

方法2:Object.assign()

javascript 复制代码
let shallowCopy = Object.assign({}, originalObject);

方法3:数组的slice()和concat()

javascript 复制代码
let shallowCopyArray = originalArray.slice();
let anotherCopy = originalArray.concat();

浅拷贝的陷阱

javascript 复制代码
let user = {
  name: "小明",
  settings: {
    theme: "dark",
    notifications: true
  }
};

let userCopy = { ...user };
userCopy.name = "小红"; // ✅ 不会影响原对象
userCopy.settings.theme = "light"; // ❌ 原对象的theme也被修改了!

console.log(user.settings.theme); // "light" 中招了!

深拷贝:连根拔起的复制

深拷贝是真正的"克隆",创建一个完全独立的新对象。

方法1:JSON大法(最简单但有局限)

javascript 复制代码
let deepCopy = JSON.parse(JSON.stringify(originalObject));

注意限制:

  • 不能复制函数、undefined、Symbol
  • 不能处理循环引用
  • Date对象会变成字符串

方法2:手写递归深拷贝函数

javascript 复制代码
function deepClone(obj, hash = new WeakMap()) {
  // 处理基本类型和null
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 处理Date
  if (obj instanceof Date) {
    return new Date(obj);
  }
  
  // 处理数组
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item, hash));
  }
  
  // 处理普通对象
  if (hash.has(obj)) {
    return hash.get(obj); // 解决循环引用
  }
  
  let cloneObj = Object.create(Object.getPrototypeOf(obj));
  hash.set(obj, cloneObj);
  
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  
  return cloneObj;
}

// 测试
let complexObj = {
  name: "测试",
  date: new Date(),
  nested: { x: 1 },
  arr: [1, 2, [3, 4]]
};
complexObj.self = complexObj; // 循环引用

let cloned = deepClone(complexObj);
console.log(cloned !== complexObj); // true
console.log(cloned.nested !== complexObj.nested); // true
console.log(cloned.self === cloned); // true,循环引用正确处理

方法3:使用现成库

javascript 复制代码
// lodash
import _ from 'lodash';
let deepCopy = _.cloneDeep(originalObject);

// 或者使用structuredClone(现代浏览器原生API)
if (typeof structuredClone === 'function') {
  let deepCopy = structuredClone(originalObject);
}

实战场景:什么时候用什么拷贝?

场景1:表单编辑(推荐深拷贝)

javascript 复制代码
// 编辑前深拷贝原始数据
let editData = deepClone(originalData);

// 用户随意编辑...
editData.user.profile.avatar = "new_avatar.jpg";

// 点击取消,原始数据完好无损
// 点击保存,提交editData

场景2:状态管理中的状态更新(浅拷贝足够)

javascript 复制代码
// Redux reducer中的状态更新
function reducer(state = initialState, action) {
  switch (action.type) {
    case 'UPDATE_SETTINGS':
      return {
        ...state, // 浅拷贝
        settings: {
          ...state.settings, // 嵌套对象也需要展开
          theme: action.payload
        }
      };
    // ...
  }
}

场景3:配置对象合并(浅拷贝+深度合并)

javascript 复制代码
function mergeConfig(defaultConfig, userConfig) {
  return {
    ...defaultConfig,
    ...userConfig,
    // 深度合并嵌套对象
    options: {
      ...defaultConfig.options,
      ...userConfig.options
    }
  };
}

性能考虑:深浅拷贝的选择

方法 速度 内存占用 适用场景
浅拷贝 ⚡⚡⚡⚡⚡(快) 简单对象,无嵌套修改
JSON深拷贝 ⚡⚡⚡(中) 数据简单,无函数/日期
递归深拷贝 ⚡⚡(慢) 复杂对象,需要完整复制
structuredClone ⚡⚡⚡⚡(较快) 现代浏览器,需要原生支持

总结:拷贝选择速查表

  1. 只需要复制第一层数据? → 使用展开运算符 ...
  2. 需要完全独立的副本? → 使用深拷贝
  3. 数据简单,不含函数/日期?JSON.parse(JSON.stringify())
  4. 需要处理复杂类型和循环引用? → 手写递归或使用lodash
  5. 现代浏览器环境? → 尝试 structuredClone

记住这个黄金法则:当你不知道嵌套属性是否需要独立修改时,选择深拷贝更安全!

互动挑战

试试看你能看出下面代码的输出吗?

javascript 复制代码
let puzzle = {
  a: 1,
  b: { inner: 2 },
  c: [3, 4]
};

let copy1 = { ...puzzle };
let copy2 = JSON.parse(JSON.stringify(puzzle));

copy1.b.inner = 999;
copy1.c.push(888);

console.log(puzzle.b.inner); // 是多少?
console.log(puzzle.c.length); // 是多少?
console.log(copy2.b.inner); // 是多少?

答案:第一个是999(浅拷贝共享嵌套对象),第二个是4(数组也被修改了),第三个是2(深拷贝完全独立)。

希望这篇博客能帮你彻底理解JavaScript中的深浅拷贝!下次遇到数据"打架"的情况,你就知道该怎么处理了。

相关推荐
AlanHou2 小时前
Three.js:Web 最重要的 3D 渲染引擎的技术综述
前端·webgl·three.js
拖拉斯旋风2 小时前
React 跨层级组件通信:使用 `useContext` 打破“长安的荔枝”困境
前端·react.js
没想好d2 小时前
通用管理后台组件库-3-vue-i18n国际化集成
前端
沛沛老爹2 小时前
Web开发者快速上手AI Agent:Dify本地化部署与提示词优化实战
前端·人工智能·rag·faq·文档细粒度
时光追逐者2 小时前
一款基于 .NET 9 构建的企业级 Web RBAC 快速开发框架
前端·c#·.net·.net core
张拭心2 小时前
"氛围编程"程序员被解雇了
android·前端·人工智能
SomUrim2 小时前
ruoyi-vue-plus中await axios报错undefined的问题(请求正常)
前端·ruoyi
daizikui2 小时前
streamlit实现登录功能
服务器·前端·javascript
广州华水科技2 小时前
如何通过单北斗形变监测一体机提高大坝安全监测效率?
前端