一、拷贝界的「表面兄弟」与「灵魂伴侣」
深浅拷贝这对CP,就像奶茶里的珍珠和芋圆——看似相似,实则本质不同。
- 浅拷贝:表面兄弟,只复制对象的「外壳」。就像你和朋友买了同款手机壳,看似一样,但手机里的聊天记录还是各自的。
- 深拷贝:灵魂伴侣,连对象的「内心世界」都复制得一模一样。相当于克隆了一个完全相同的你,连你藏在备忘录里的暗恋对象都复制过去了。
二、V8引擎的「储物间」:栈内存与堆内存
要理解拷贝,得先知道JavaScript的数据都存哪儿。V8引擎有两个「储物间」:
- 栈内存:像超市的小储物柜,存着基本数据类型(数字、字符串等)和引用数据类型的「地址牌」。
- 堆内存:像大型仓库,存着引用数据类型的「真身」(对象、数组等)。
当你复制一个对象时,浅拷贝只复制了「地址牌」,深拷贝则是连「仓库里的东西」都复制了一份。
三、浅拷贝:6种「表面功夫」哪家强?
1. Object.create(obj):「认干爹」法
const obj = { name: '康少', age: 18 };
const obj2 = Object.create(obj); // obj2认obj当干爹
console.log(obj2.name); // '康少'(继承干爹的财产)
2. [].concat(arr):「黏合剂」法
const arr = [1, 2, 3];
const newArr = [].concat(arr); // 用空数组黏合原数组
3. 数组解构 [...arr]:「分身术」法
const arr = [1, 2, 3];
const newArr = [...arr]; // 打散原数组,再组装成新数组
4. arr.slice(0, arr.length):「一刀切」法
const arr = ['a', 'b', 'c'];
const newArr = arr.slice(0, arr.length); // 从开头切到结尾,得到一个新数组
5. Object.assign({}, obj):「资产重组」法
const obj = { name: '康少', age: 18 };
const newObj = Object.assign({}, obj); // 把obj的财产转移到新对象
6. arr.toReversed().reverse():「折腾主义」法
const arr = [1, 2, 3];
const newArr = arr.toReversed().reverse(); // 先反转,再反转回来,得到新数组
注意:以上方法都只能拷贝对象的最外层。如果对象里还有对象(嵌套对象),修改原对象的内层属性,新对象也会跟着变。就像你复制了一份文件夹,里面的子文件夹还是指向原来的位置。
四、深拷贝:「灵魂复制」的三种姿势
1. JSON.parse(JSON.stringify(obj)):「JSON大法」
const obj = { name: '康少', age: 18, like: { a: '唱', b: '跳' } };
const newObj = JSON.parse(JSON.stringify(obj)); // 先转成JSON字符串,再转回对象
但这招有三个致命缺点:
- 无法识别bigint类型
- 无法处理undefined、symbol、function
- 遇到循环引用直接「罢工」
2. structuredClone():「官方认证」法
const obj = { name: '康少', age: 18, like: { a: '唱', b: '跳' } };
const newObj = structuredClone(obj); // 浏览器自带的深拷贝API
这招比JSON大法强,能处理循环引用,但仍不支持function。
3. 手写深拷贝:「手动搓一个灵魂」
function deepCopy(obj) {
// 处理null和非对象类型
if (obj === null || typeof obj !== 'object') return obj;
// 处理数组
if (Array.isArray(obj)) {
const newArr = [];
for (let i = 0; i < obj.length; i++) {
newArr[i] = deepCopy(obj[i]);
}
return newArr;
}
// 处理对象
const newObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepCopy(obj[key]);
}
}
return newObj;
}
这是终极解决方案,但还可以优化:
- 处理Date、RegExp等特殊对象
- 使用Map缓存已拷贝对象,避免循环引用
五、总结:拷贝界的「择偶标准」
- 如果你只是想复制一个简单对象,用浅拷贝就够了(推荐
Object.assign或解构赋值)。 - 如果你要复制的对象里有嵌套对象,且没有特殊类型,用
structuredClone。 - 如果你要处理复杂对象或循环引用,就得手写深拷贝了。
记住:深浅拷贝的核心区别,在于是否拷贝了引用类型的「真身」。理解了这一点,面试时再被问到,你就能侃侃而谈了!
(偷偷说一句:如果面试官问你怎么处理循环引用,你就把上面的深拷贝函数拿出来,再加上Map缓存,保证他对你刮目相看!)