一、为什么要拷贝对象?
想象一下,你有一份重要的文件(对象),你想给朋友一份副本。
如果只是把文件夹路径发给他(浅拷贝),你俩其实用的是同一份文件,谁改了内容都会影响对方。
如果你给他一份真正的复印件(深拷贝),你们互不影响,各玩各的。
二、JS里的数据存储和类型
- 基本类型(number、string、boolean、null、undefined、symbol、bigint):直接存储在栈内存,赋值就是复制一份,互不影响。
- 引用类型(对象、数组、函数等):存储在堆内存,变量里存的是“地址”,赋值只是复制地址,指向同一个对象。
三、浅拷贝:只复制一层,容易“牵一发动全身”
1. 常见浅拷贝方法
Object.assign({}, obj)...扩展运算符([...arr]、{...obj})Array.prototype.slice()Array.prototype.concat()Object.create(obj)arr.toReversed().reverse()
2. 手写一个浅拷贝
function shallowCopy(obj) {
let newObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
3. 浅拷贝的坑
let obj = {
name: 'Ricardo',
like: { a: '唱', b: '跳' }
};
let newObj = shallowCopy(obj);
obj.like.a = '篮球';
console.log(newObj.like.a); // '篮球',浅拷贝只复制了第一层,内部对象还是同一个!
四、深拷贝:层层复制,互不影响
1. 常见深拷贝方法
JSON.parse(JSON.stringify(obj))
缺点:丢失undefined、function、symbol,不能处理循环引用,bigint也不行。structuredClone(obj)
优点:支持循环引用、undefined、bigint,但function、symbol还是丢失。
2. 手写一个简单深拷贝
function deepCopy(obj) {
let newObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
newObj[key] = deepCopy(obj[key]);
} else {
newObj[key] = obj[key];
}
}
}
return newObj;
}
3. 深拷贝的效果
let obj = {
name: 'Ricardo',
like: { a: '唱', b: '跳' }
};
let newObj = deepCopy(obj);
obj.like.a = '篮球';
console.log(newObj.like.a); // 还是'唱',深拷贝互不影响!
五、各种拷贝方法的优缺点对比
| 方法 | 是否深拷贝 | 支持循环引用 | 支持function/symbol | 支持bigint | 速度 |
|---|---|---|---|---|---|
| Object.assign/扩展运算符 | 否 | - | 是 | 是 | 快 |
| JSON.parse(JSON.stringify) | 是 | 否 | 否 | 否 | 较快 |
| structuredClone | 是 | 是 | 否 | 是 | 快 |
| 手写递归 | 是 | 否 | 取决于实现 | 取决于实现 | 慢 |
六、面试高频陷阱
- 浅拷贝后改内部对象,两个对象都变?
- JSON深拷贝丢失特殊类型?
- 循环引用导致JSON方法报错?
- 如何优雅处理大对象的深拷贝?
七、实际开发建议
- 只需要复制一层,用浅拷贝,速度快。
- 需要彻底隔离,用深拷贝,优先
structuredClone。 - 兼容性要求高时,手写递归。
- 注意特殊类型和循环引用,别一味用JSON方法。
八、总结
拷贝对象看似简单,实则暗藏玄机。浅拷贝适合简单场景,深拷贝才是真正的“断舍离”。理解原理、掌握方法,才能在开发和面试中游刃有余。下次再遇到“拷贝”相关问题,你一定能自信应对!