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

📚 拷贝不只是复制粘贴
想象一下,你有一本心爱的精装书,朋友想借去阅读。你有两个选择:
- 直接给朋友 - 但书就不在你手上了(原始引用)
- 去复印店复印一本 - 朋友有自己的副本,你的原版还在
在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() |
⚡⚡⚡ | 深 | 较好 | ✅ |
💡 实用小贴士
-
"先问要不要,再问怎么做" - 先确定是否需要深拷贝,很多情况浅拷贝就够用了
-
现代JS的救星:
javascript// 浏览器和Node.js的新宠 const cloned = structuredClone(original); // 支持大部分类型! -
Lodash是你的好朋友:
javascriptimport { cloneDeep } from 'lodash-es'; // 按需引入 // 或者用 throttle 的深拷贝函数 -
性能提示:对于超大对象,考虑是否需要整个拷贝,也许只需修改部分
🎬 实战演练:拷贝在真实场景的应用
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个
📝 总结:拷贝选择指南
- "我只想改改表面" → 用浅拷贝(
...或Object.assign) - "我要完全独立的新对象" → 用深拷贝
- "我有特殊类型或循环引用" → 用
structuredClone()或Lodash - "不确定深浅" → 问问自己:嵌套对象需要独立吗?
记住,在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,因为浅拷贝共享了嵌套对象!