写ref="/tag/137/" style="color:#3D6345;font-weight:bold;">JavaScript的时候,经常会遇到要复制一个对象的情况。比如你做了一个用户表单,填完信息想存个副本,这时候直接用等号赋值,改副本的时候原数据也变了——问题就出在你用了浅拷贝。
赋值不等于复制
很多人一开始都会犯这个错误:
const user = { name: '张三', info: { age: 25 } };
const copy = user;
copy.name = '李四';
console.log(user.name); // 输出 '李四'
看到没?你改的是copy,user也跟着变了。因为这只是把引用地址传过去了,两个变量指向同一个对象,这根本不是复制。
浅拷贝:只复制一层
真正的拷贝得另想办法。浅拷贝能做到第一层属性的独立,但嵌套的对象还是共用的。
const user = { name: '张三', info: { age: 25 } };
const copy = Object.assign({}, user);
// 或者用解构
// const copy = { ...user };
copy.info.age = 30;
console.log(user.info.age); // 输出 30
name能分开改,但info是对象,它内部的age还是共享的。这就是浅拷贝的坑:看起来分开了,其实里子还连着。
深拷贝:彻底断开联系
要想完全独立,就得深拷贝。最简单的方法是序列化再解析,适合纯数据对象:
const user = { name: '张三', info: { age: 25 } };
const copy = JSON.parse(JSON.stringify(user));
copy.info.age = 30;
console.log(user.info.age); // 输出 25,终于不影响了
但这招有局限。函数、undefined、Symbol会被丢掉,Date类型会变成字符串,循环引用还会报错。真要用在复杂场景,得上专门的库,比如 lodash 的 cloneDeep。
实际场景中的选择
你在写一个购物车功能,用户点“保存当前状态”,你得把当前商品列表存下来。如果只是浅拷贝,后面修改商品数量时,历史记录也会变,用户一刷新发现之前的数据不对劲,这就糟了。
但如果数据结构简单,比如只有商品ID和数量,没有嵌套配置,那用展开运算符 {...cart} 完全够用,性能还好。
自己实现一个简易深拷贝
了解原理也有必要。下面是个递归实现的基本版本:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) {
return obj.map(item => deepClone(item));
}
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
这个函数能处理基本的嵌套对象和数组,虽然不包括Map、Set这些特殊类型,但在多数业务逻辑里已经够用了。
深拷贝和浅拷贝不是选哪个高级的问题,而是看你要不要彻底切断关联。搞清楚它们的区别,才能避免莫名其妙的数据污染问题。