当JS拷贝玩起了“俄罗斯套娃”:深拷贝与浅拷贝的趣味对决

欢迎使用我的小程序👇👇👇👇


📚 拷贝不只是复制粘贴

想象一下,你有一本心爱的精装书,朋友想借去阅读。你有两个选择:

  1. 直接给朋友 - 但书就不在你手上了(原始引用)
  2. 去复印店复印一本 - 朋友有自己的副本,你的原版还在

在JavaScript的世界里,拷贝数据就像这个场景,但有更多的"套娃"情况!

🎭 浅拷贝:只搬家的"表面朋友"

浅拷贝就像只搬走了家具,但墙上还挂着原房子的钥匙:

javascript 复制代码
// 原始对象 - 一个"套娃"对象
const original = {
  name: "小明",
  age: 25,
  hobbies: ["编程", "游戏", "阅读"], // 注意这个数组!
  address: {
    city: "北京",
    district: "海淀区"
  }
};

// 浅拷贝的几种方式:
const shallowCopy1 = Object.assign({}, original);
const shallowCopy2 = { ...original }; // 扩展运算符
const shallowCopy3 = original.slice?.(); // 数组专用

// 试试修改会发生什么?
shallowCopy1.name = "小红"; // ✅ 不会影响original
shallowCopy1.hobbies.push("摄影"); // ⚠️ 糟糕!original的hobbies也被改了!

浅拷贝的特点:

  • 只复制第一层属性
  • 嵌套对象/数组仍然是"共享"的引用
  • 像只换了外壳,芯子还是同一个

🧳 深拷贝:真正的"独立门户"

深拷贝就像带着所有家当搬到了全新的房子:

javascript 复制代码
// 深拷贝实现方式大比拼

// 方法1:JSON大法(最简单但有局限)
const deepCopy1 = JSON.parse(JSON.stringify(original));

// 方法2:递归实现(自己动手,丰衣足食)
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  
  const clone = Array.isArray(obj) ? [] : {};
  
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key]);
    }
  }
  
  return clone;
}

const deepCopy2 = deepClone(original);

// 方法3:使用现成库(最省心)
// const deepCopy3 = _.cloneDeep(original); // Lodash
// const deepCopy4 = structuredClone(original); // 现代JS原生API

现在无论怎么修改deepCopy,都不会影响original了!

🎯 什么时候用什么?

浅拷贝适用场景:

javascript 复制代码
// 场景1:配置合并
const defaultConfig = { theme: 'light', showTips: true };
const userConfig = { theme: 'dark' };
const finalConfig = { ...defaultConfig, ...userConfig };

// 场景2:创建对象副本进行简单修改
const user = { name: '张三', loggedIn: false };
const updatedUser = { ...user, loggedIn: true };

深拷贝适用场景:

javascript 复制代码
// 场景1:状态管理(如Redux reducer)
function reducer(state, action) {
  switch(action.type) {
    case 'UPDATE_USER':
      return {
        ...state,
        user: deepClone(action.payload) // 确保完全独立
      };
  }
}

// 场景2:表单数据的初始副本
const initialFormData = deepClone(templateData);
const formData = deepClone(initialFormData); // 每次都是新的开始

🧪 趣味实验:拷贝的陷阱

javascript 复制代码
// 陷阱1:循环引用(自己引用自己)
const narcissist = { name: "自恋对象" };
narcissist.self = narcissist; // 我引用我自己!

// JSON大法会报错!
// JSON.parse(JSON.stringify(narcissist)); // TypeError!

// 陷阱2:特殊对象
const specialObj = {
  date: new Date(),
  regex: /hello/gi,
  func: function() { return "Hi"; },
  undefined: undefined,
  infinity: Infinity,
  nan: NaN
};

console.log(JSON.parse(JSON.stringify(specialObj)));
// 函数、undefined不见了!日期变成了字符串...

📊 性能对比:速度与深度的博弈

方法 速度 深度 特殊类型支持 循环引用支持
扩展运算符 ... ⚡⚡⚡⚡⚡ 有限
Object.assign() ⚡⚡⚡⚡⚡ 有限
JSON方法 ⚡⚡⚡
递归实现 ⚡⚡
Lodash的cloneDeep ⚡⚡ 很好
structuredClone() ⚡⚡⚡ 较好

💡 实用小贴士

  1. "先问要不要,再问怎么做" - 先确定是否需要深拷贝,很多情况浅拷贝就够用了

  2. 现代JS的救星

    javascript 复制代码
    // 浏览器和Node.js的新宠
    const cloned = structuredClone(original); // 支持大部分类型!
  3. Lodash是你的好朋友

    javascript 复制代码
    import { cloneDeep } from 'lodash-es'; // 按需引入
    // 或者用 throttle 的深拷贝函数
  4. 性能提示:对于超大对象,考虑是否需要整个拷贝,也许只需修改部分

🎬 实战演练:拷贝在真实场景的应用

javascript 复制代码
// 购物车场景
const shoppingCart = {
  items: [
    { id: 1, name: "JavaScript高级编程", quantity: 1, price: 99 },
    { id: 2, name: "TypeScript入门", quantity: 2, price: 79 }
  ],
  discount: 0.1,
  getTotal() {
    return this.items.reduce((sum, item) => 
      sum + item.price * item.quantity, 0) * (1 - this.discount);
  }
};

// 用户想"如果这样买"的试算功能
function whatIfAddItem(cart, newItem) {
  const hypotheticalCart = deepClone(cart);
  hypotheticalCart.items.push(newItem);
  return hypotheticalCart.getTotal();
}

// 真实的购物车不受影响
console.log(shoppingCart.items.length); // 还是2个

📝 总结:拷贝选择指南

  1. "我只想改改表面" → 用浅拷贝(...Object.assign
  2. "我要完全独立的新对象" → 用深拷贝
  3. "我有特殊类型或循环引用" → 用structuredClone()或Lodash
  4. "不确定深浅" → 问问自己:嵌套对象需要独立吗?

记住,在JavaScript的世界里,"拷贝"不是简单的复制粘贴,而是关于"独立性"的哲学选择。选择正确的拷贝方式,能让你的代码更健壮、更可预测!

下次当你面对需要拷贝的场景时,不妨先想想:这是一个需要独立门户的深拷贝,还是一个可以共享芯子的浅拷贝?


小测验:你能看出下面代码的输出吗?

javascript 复制代码
const obj = { a: 1, b: { c: 2 } };
const shallow = { ...obj };
const deep = JSON.parse(JSON.stringify(obj));

shallow.b.c = 999;
deep.b.c = 888;

console.log(obj.b.c); // 是多少?

答案:999,因为浅拷贝共享了嵌套对象!

相关推荐
code_YuJun5 小时前
脚手架开发工具——npmlog
前端
donecoding5 小时前
掌握 :focus-within,让你的AI对话输入体验更上一层楼!
前端·人工智能
快乐星球喂5 小时前
使用nrm管理镜像
前端
用户4099322502126 小时前
Vue3动态样式管理:如何混合class/style绑定、穿透scoped并优化性能?
前端·ai编程·trae
小徐不会敲代码~6 小时前
Vue3 学习2
前端·javascript·学习
m0_740043736 小时前
Vue2 语法糖简洁指南
前端·javascript·vue.js
zhougl9966 小时前
区分__proto__和prototype
开发语言·javascript·原型模式
天天向上10246 小时前
成功阻止chrome浏览器自动填充密码
服务器·前端·chrome