深浅拷贝听起来非常抽象,要理解他也有点困难。
理解的顺序 => 思考案例 :为什么会出现这种情况=> 了解深浅拷贝概念 => 堆栈存储原理 => 深浅拷贝案例 => 总结
先看案例:
let example1 = 12;
let example2 = example1;
example2 = 22;
let exampleObj1 = { name:"nihao" ,age: 18 };
let exampleObj2 = exampleObj1;
exampleObj2.name = "cheng-xu-yuan";
console.log(example1, example2);
console.log(exampleObj1, exampleObj2);
好的,现在来思考一下结果会是什么呢…
好了吗,我公布答案了。
答案:
> 12 22
> Object { name: "cheng-xu-yuan", age: 18 } Object { name: "cheng-xu-yuan", age: 18 }
接着思考,我改的example2,怎么连example1的值也同时改变了呢?那我要怎样才能做到example1和example2的修改不会相互影响呢?(这个就是深浅拷贝出现的原因了)
下面聊些概念:
浅拷贝:拷贝 -> 复制 ,浅 -> 随意,任意。针对的是引用数据类型,副作用(复制后修改任意一元素的值,另一个值的相应元素值跟着改变)很明显,效果参照上述案例exampleObj1、exampleObj2的结果
深拷贝:针对浅拷贝的副作用对症下药的。它没有浅拷贝的副作用,但是具备浅拷贝的优势(可以拷贝)。
至于为啥会出现刚刚的问题就涉及到变量在堆栈上的存储机制了,先来了解点小知识:
javascript的基本数据类型都是按值存储在栈内存,引用数据类型是存储在堆内存中。
基本数据类型:string、number、boolean、null、undefined
引用数据类型:function、array、object
带着小知识,开工!!
堆栈存储原理:
案例:
let example1 = 12;
let example2 = example1;
let exampleObj1 = { name:"nihao", age: 18 };
let exampleObj2 = exampleObj1;
来看看堆栈里发生了什么

解析:
代码执行 let example1 = 12; 时,程序会在栈内存里开辟两块空间,分别存放example1 和 值12,然后example1中存放的是一个地址,值12也有自己的一个内存地址,example1中的地址指向了存放值12的地址,可以通过example1中的地址去访问存放值12的地址,来拿到地址中的值12,所以我们认为example1就是12(类似内存把值12关在一个小房子里,然后给了example1一把钥匙,example1就可以通过钥匙来找到值12)。
然后执行let example2 = example1;
程序一看,哎,example1?我有啊,他对应的值是12嘛,那我就再开辟一块空间好了,存放的值还是12,不过地址已经变成了另外一个地址了,然后就接着给example2开辟一块空间,存放的同样也是一个地址,这个地址指向刚刚新生成的那块空间的值,这样就可以通过example2去拿刚刚的那个值了。
如果不看这些,光看代码是不是以为example1 和example2就是一个东西啦,嘿嘿,别着急,接着往下看 ----->
接着执行代码
let exampleObj1 = { name:“nihao”, age: 18 };
结合小知识发现,和刚刚的变量不一样,这个是对象!应该存放在堆内存中。所以程序就开始工作了,在堆内开辟一块空间,存放这个对象,然后栈内也开辟一块空间,exampleObj1存放的依然是一个地址,那我们怎么去访问堆里面的值呢?毕竟都不在同一个地方呢,然后程序就在栈内创建一个地址,这个地址对应堆内的对象,我们就可以通过这个地址来访问该对象,我们只需要将刚刚创建的地址赋值给exampleObj的地址对应的值就可以实现exampleObj1和对象关联起来了。
最后执行代码
let exampleObj2 = exampleObj1;
程序一看,哎,exampleObj1?而且值一个在堆内存一个在栈内存,巧了,他对应的堆里的值和栈地址我还有,那我就偷点懒好了,就开辟了一块空间,存放exampleObj2对应的地址,然后把这个地址和刚刚exampleObj1的地址对应的栈内地址关联起来,这样就可以通过exampleObj2和刚刚的对象关联起来,最终通过exampleObj2访问该对象了。
看到这同学们应该就很清楚刚刚的案例为啥会出现复制后修改任意一个的值另一个也跟着变的原因了。
注意:浅拷贝的对象是基本数据类型不会出现副作用,只有拷贝引用数据类型时才会有副作用
接着我们来看看一些案例:
浅拷贝:slice、concat、object.assign、扩展运算符(...)
let arr1 = [1,2,{age:18}];
let newArray1 = arr1.slice();
newArray1[0] = 12;
newArray1[2].age = 19;
console.log(arr1, newArray1);
> Array [1, 2, Object { age: 19 }] Array [12, 2, Object { age: 19 }]
let arr2 = [2,6,{name:"ni-hao"}];
let newArray2 = arr2.concat();
newArray2[0] = 20;
newArray2[2].name = "hao-de";
console.log(arr2, newArray2);
> Array [2, 6, Object { name: "hao-de" }] Array [20, 6, Object { name: "hao-de" }]
let obj1 = {};
let obj2 = { name: "obj1",age:12,class: { currentClass: 19, oldClass: 3}};
Object.assign(obj1,obj2);
obj1.name = "isObj1";
obj2.class.currentClass = 18;
console.log(obj1,obj2);
> Object { name: "isObj1", age: 12, class: Object { currentClass: 18, oldClass: 3 } } Object { name: "obj1", age: 12, class: Object { currentClass: 18, oldClass: 3 } }
let arr3 = [2,6,{name:"ni-hao"}];
let obj3 = { name: "obj3",age:33,class: { currentClass: 12, oldClass: 1}};
let newArray3 = [...arr3];
let newObj3 = {...obj3};
newArray3[0] = 22;
newArray3[2].name = "hen-hao";
newObj3.name = "newObj3";
newObj3.class.oldClass = 2;
console.log(arr3,newArray3);
> Array [2, 6, Object { name: "hen-hao" }] Array [22, 6, Object { name: "hen-hao" }]
console.log(obj3,newObj3);
> Object { name: "obj3", age: 33, class: Object { currentClass: 12, oldClass: 2 } } Object { name: "newObj3", age: 33, class: Object { currentClass: 12, oldClass: 2 } }
深拷贝:JSON.parse(JSON.stringify())、手写递归、lodash的cloneDeep
let obj4 = { name: "obj3",age:33,class: { currentClass: 12, oldClass: 1}};
let obj5 = JSON.parse(JSON.stringify(obj4));
obj4.class.currentClass = 44;
obj5.class.oldClass = 55;
console.log(obj4,obj5);
function deepClone(sourceObj) {
if (sourceObj instanceof Object == false) return sourceObj;
let targetObj = Array.isArray(sourceObj) ? [] : {}; // 判断是否为数组
for (let i in sourceObj) { // i 代表对象中的属性,数组中的下标
if (sourceObj.hasOwnProperty(i)) { //判断对象是否拥有这个属性
if (typeof sourceObj[i] === 'object') {
targetObj[i] = deepClone(sourceObj[i]);
} else {
targetObj[i] = source[i];
}
}
}
return targetObj;
}
lodash的cloneDeep大家感兴趣的话就自己去看看叭,这里就不过多介绍了😄
好啦,文章到这就快结束了,本文重点就是堆栈存储原理,理解了那个再来理解深浅拷贝就简单多了。全文关键字:引用数据类型,只有引用数据类型才可能出现拷贝副作用。
总结:引用数据类型浅拷贝->复制完前后两个数据会相互影响,引用数据类型深拷贝->复制完前后两个数据不会互相影响
如果到这还没懂的话…
那我举个大白话的例子:
对与引用数据类型来说,就像两个学生考试,一个成绩还不错的A,一个是他好兄弟B,成绩好的A做好了(相当于内存开辟完了空间,也对值和变量进行了关联 ),另一个同学B就进行所谓的“借鉴”,此时的浅拷贝就是: B同学将A同学的答案尽数复制,复制完成后,突然A同学说,我第12道题做出来了,之前是蒙的,那题选A哈,不是选D,B同学一听,眼里满含泪水,心理不禁想:够兄弟,随即大手一挥,将答案改成了A,后来B同学看到第11题的题目,嘶~,好熟悉,随即拿出资料书翻阅,我的天,原题!!! 想到刚刚好兄弟A的行为,就算被抓了也要告诉A这题不是C,是选B。A一听,说:我就是有点犹豫是B还是C,感觉好熟悉,原题选C是吧,好的,拿捏了👌(浅拷贝:拷贝引用数据类型前后会相互影响)。此时的深拷贝就是:(假设B同学不存在的情况下)还有另外一个同学C,他成绩一直不太行,他这次本来名字都不想写来着,但是考试之前老师 说了啊,没交卷没写满就告诉班主任,让班主任通知家长领回去啊,这个C同学就有点紧张了,就对A说,你做完了给我一份,A同学说好(作者不让他说不好的😁),等A同学做完了,C同学尽数复制,复制好了就等老师说交卷了,他也不想得更高分,他连试卷都不想做,更别说去帮A看看有没有原题什么的了,A同学知道他的成绩什么的,根本没指望C同学能给他复制一些题目的答案了。所以C同学改一些答案(为了查重)什么的A同学自然也不会管,A同学修改的个别题目的答案C同学也不感兴趣,这个就是深拷贝的原理了(深拷贝:拷贝引用数据类型前后不会相互影响)。
好啦,文章到这就结束了🥳🥳,最后这个大白话案例我咋知道的?就算你们打断我的肋骨熬汤喝我也不会承认我是C同学的🤪