前言
js的深拷贝,浅拷贝问题在项目的开发过程中是频繁出现的。对于一个合格的前端开发者来说,对于深刻理解它们是必要的,并能合理使用深拷贝浅拷贝处理问题。
什么是深拷贝,什么是浅拷贝
以下我对于它们的理解是:
深拷贝:如果是对象a和对象b它们是深拷贝,那么它们不再引用统一内存地址了,也就是它们 老死不相往来了。
浅拷贝:对象a只是复制指向对象b的指针,而不复制对象本身。新旧对象它们还是共享同一内存。
好了,在脑海里大致知道有这么一个小的概念之后,我们来聊聊更深入的内存。
javascript的堆栈
堆&栈
两者都是存放临时数据的地方。
栈是先进后出的,就像一个桶,后进去的先出来,它下面本来有的东西要等其他出来之后才能出来。
堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。对于堆,我们可以随心所欲的进行增加变量和删除变量,不用遵循次序。
栈区(stack) 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。 堆区(heap) 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。 堆(数据结构):堆可以被看成是一棵树,如:堆排序; 栈(数据结构):一种先进后出的数据结构。
数据类型访问&&复制
基本数据类型:定义在栈中的,从栈当中直接存取变量的值。
例如:var a =1;
栈内存
a
1
基本数据类型复制:复制时,会在栈中创建一个新值,然后把值复制到为新变量分配的位置上。
例如:var b=a;
栈内存
a
1
b
1
b=2
栈内存
a
1
b
2
引用数据类型:定义在栈中的,但它的值指向堆当中的一个地址。
例如:
let obj=Object.create(null);
obj={a:1};
复制代码
栈内存
堆内存
obj
{a:1}
引用数据类型复制:复制的是存储在栈中的指针,将指针复制到栈中并且为新变量分配空间。而这个指针副本和原指针指向存储在堆中的同一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,在使用时,改变其中的一个变量的值,将影响另一个变量。
例如:
let obj=Object.create(null);
obj={a:1};
let obj2=obj;
复制代码
好了,了解了数据结构之后,那我们来谈谈如何实现深浅拷贝。
如何实现深浅拷贝
毋庸置疑,浅拷贝的实现是大多数人都能想到的,因为在项目的开发的过程当中,就是有时候因为数据浅拷贝而引发一系列的数据问题。
源数据data:
let data={
name:'ddd',
type:'es',
impl:[
{
e:1,
a:2
},
{
e:2,
a:3
}
]
}
复制代码
如何实现浅拷贝
第一种情况:将数据源data直接赋值给另外一个对象。 猜猜下面代码结果
let obj=data;//浅拷贝
obj.name='aa'//当obj修改name的值之后,data的name值也发生改变
console.log(obj1,data);
复制代码
第二种情况:将数据源data做为另一个对象的子对象。猜猜下面代码结果
let obj1={data};//定义obj1是一个对象,并且将data是obj1的子对象。
obj1.name='bb'//给obj1添加name属性,data的name值不发生改变
obj1.data.impl[0].e=4; //但是修改obj的data属性的值,数据源data会发生改变
obj1.data.name='pp';
console.log(obj1,data);
复制代码
第三种情况:使用es6的扩展符号{...XXX}。猜猜下面代码结果
let obj2={...data};
obj2.name='cc';
obj2.impl[0].e=3
console.log(obj2,data);
复制代码
第三种的情况是特殊的,但是这种在JavaScript当中是常见的。有时候是深拷贝,有时候是浅拷贝,只有当第一层属性值不是对象的时候则是深拷贝否则就是浅拷贝。 总结一下有时候深拷贝有时候浅拷贝的特殊语法:
1、for in 遍历循环拷贝
2、es6 Object.assign({ },obj)
3、{… obj}
4、concat
5、slice
好吧,上面三种情况的结果依次如下:
第一种情况:
第二种情况:
第三种情况:
那如何才能实现真正的拷贝呢?也就是真的老死不相往来了,一点关系也没有了呢?
如何实现深拷贝
1、利用JSON对象的parse和stringfy
JSON.stringfy是将一个js对象值转成一个JSON字符串,JSON.parse是将一个JSON字符串转成一个js对象。先转成字符串再转对象,所生成的对象和原来的对象不共用同一内存,实现深拷贝的过程。
但是有个问题:undefined、function、symbol在转化中会被忽略。
2、利用递归函数来实现
实现思路就是循环对象,判断对象的属性值是否还是对象,如果还是那就递归。
function cloneData(obj){
let target;
if(typeof obj==='object' && obj!==null){
if(Array.isArray(obj)){
target=[];
}else{
target={};
}
}else{
return target=obj;
}
for(let key in obj){
if(obj.hasOwnProperty(key)){
if(typeof obj[key] ==='object'){
target[key]=cloneData(obj[key]);
}else{
target[key]=obj[key];
}
}
}
return target;
}
复制代码
3、loadsh第三方库来实现 _.cloneDeep
引入第三方库,使用现成api实现深拷贝。
var obj={
a:{
b:2,
c:3
}
}
var obj1=_.cloneDeep(obj);
obj1.a.c=6;
复制代码
总结
以上就是我对深拷贝浅拷贝的理解,上面的代码是无聊的时候瞎写的,小伙伴可以自己动手写一下,有意外的收获的~如果上述的内容有帮到你,那就给我点个赞哈~感谢啦!!