对javascript深拷贝、浅拷贝和循环引用的深刻理解
1.为什么有深本和浅本?
这从js中的数据类型开始,分为基本数据类型和引用数据类型。
基本类型值是指存储在堆栈内存中的简单数据段,也就是说,这些值完全存储在内存中的某个位置。包括数字、字符串、布尔值、空值、未定义、符号。
引用类型值引用存储在堆内存中的对象,因此引用类型值保存指向存储在堆中的对象的指针。除了以上六种基本数据类型,其余都是引用类型,统称为Object类型。细分下来,有:对象类型、数组类型、日期类型、正则表达式类型、函数类型等。
由于引用类型的这种机制,当我们将引用类型的值从一个变量复制到另一个变量时,我们实际上是将堆栈内存中这种引用类型的引用地址复制到新的变量,这实际上是一个指针。因此,当操作完成时,这两个变量实际上指向堆内存中的同一个对象。如果您更改其中一个对象,另一个对象也会更改。
因此,深度复制和浅复制只出现在引用类型中。简单来说,它们的区别在于:
1.等级制度
浅层复制只是依次复制对象的属性,而不是递归复制,也就是说,它只分配目标对象的第一个属性。深度复制不同于浅复制,它不仅复制目标对象的第一层属性,还递归复制目标对象的所有属性。2.是否打开新堆栈
浅拷贝是对目标对象第一层基本数据类型的数据的直接赋值,即“值传递”;至于目标对象第一层的引用数据类型的数据,是直接存储在堆栈内存中的堆内存地址,也就是“地址转移”,并不会打开新的堆栈,也就是复制的结果,两个对象指向同一个地址,如果一个对象的属性被修改,另一个对象的属性也会发生变化,而深度复制和深度复制会打开一个新的堆栈。两个对象对应两个不同的地址,修改一个对象的属性不会改变第二个,浅拷贝
以下是实现浅层复制的几种方法:
1.Array.concat()
const arr=[1,2,3,4,[5,6]];const copy=arr . concat();\ \用concat () \ \创建arr的副本更改基本类型值,而不更改原始数组副本[0]=2;arr//[1,2,3,4,[5,6]];\ \改变数组中的引用类型值,原数组也会改变副本[4][1]=7;arr//[1,2,3,4,[5,7]];类似的效果可以通过slice()和Array.from()等实现。你可以自己试试~
2.Object.assign()
const obj1={x: 1,y : 2 };const obj2=Object.assign({},obj 1);obj 2 . x=2;\ \修改obj2.x,更改基本类型值console.log (obj1)//{x: 1,y : 2 }//原始对象未更改console . log(obj 2)/{ x : 2,y: 2} const obj1={x33330 2。const obj2=Object.assign({},obj 1);obj 2 . y . m=2;\ \修改obj2.y.m,并更改引用类型值console . log(obj 1)/{ x : 1,y: {m: 2}}原始对象也更改了console . log(obj 2)/{ x : 2,y: {m3330}
1.JSON.parse()和JSON.stringify()
const obj1={ x: 1,y: { m: 1 } }const obj 2=JSON . parse(JSON . stringify(obj 1));console . log(obj 1)/{ x : 1,y : { m : 1 } } console . log(obj 2)/{ x : 1,y : { m : 1 } } obj 2 . y . m=2;//修改obj 2 . y . mconsole . log(obj 1)/{ x : 1,y: {m: 1}}原始对象已被JSON更改console . log(obj 2)/{ x : 2,y: {m: 2}}
未定义的、任意的函数、正则表达式类型和符号值在序列化过程中被忽略(当它们出现在非数组对象的属性值中时)或转换为null(当它们出现在数组中时);它丢弃对象的构造函数。即深度复制后,无论这个对象的原始构造函数是什么,深度复制后都会变成对象;如果对象中存在循环引用,则无法正确处理。2.递归
函数deepCopy1(obj) {//创建一个新的对象let result={} let keys=object。keys (obj),key=null,temp=nullfor(设I=0;长度;I){ key=keys[I];temp=obj[key];//如果字段的值也是对象,递归操作if (temp的temp类型==' object '){ result[key]=deep copy(temp);} else {//否则直接赋给新对象结果[key]=temp;} }返回结果;}const obj1={ x: { m: 1 },y:未定义,z:函数add(z1,z2) { return z1 z2 },a : Symbol(' foo ')};const obj 2=DeepCopy 1(obj 1);obj 2 . x . m=2;console . log(obj 1);//{x: {m: 1},y: undefined,z:a : Symbol(foo)} console . log(obj 2);//{x: {m: 2},y:未定义,z3360,a:符号(foo)} IV。循环引用
看来递归已经完全解决了我们的问题,但是还有一种情况我们没有考虑到,那就是循环引用
1.父引用
这里的父引用是指当一个对象的属性是对象本身时,如果我们在此时进行深度复制,它可能会继续在子元素-父对象-子元素的循环中.导致堆栈溢出。例如,以下示例:
const obj1={ x: 1,y : 2 };obj 1 . z=obj 1;const obj 2=DeepCopy 1(obj 1);\ \堆栈溢出的解决方案是:只需要判断一个对象的字段是引用这个对象还是引用这个对象的任意父对象,可以修改上面的deepCopy函数:
函数deepcopy2 (obj,parent=null){//创建一个新的对象let result={ };让key=object . keys(obj),key=null,temp=null,_ parent=parent//如果字段有父项,需要追溯到它的父项,而(_parent) {//如果字段引用了它的父项,如果(_parent)则是循环引用。origin parent==obj){//循环引用返回新的同级对象,return _ parent.currentParent} _parent=_parent.parent } for(让i=0,len=keys.length伊琳;I) {key=keys[i] temp=obj[key] //如果字段的值也是一个新的对象if(temp==' object '){ result[key]=deepcopy(temp,{//递归执行deep copy,将同级待复制的对象和新的对象转移到父对象,以便于origin parent: obj,current parent: result,parent: parent}的回溯循环引用);} else { result[key]=temp;} }返回结果;} const obj 1={ x :1 } obj 1 . z=obj 1;const obj 2=DeepCopy 2(obj 1);2.同行参考
让我们假设对象obj有三个子对象:a、b和c,子对象c中的一个属性d引用对象obj下面的子对象a。
const obj={ a: { name: 'a' },b: { name: 'b' },c : { };c . d . e=obj . a;此时,c.d.e和obj.a是相等的,因为它们指的是同一个对象
console . log(c . d . e===obj . a);//true如果我们调用上面的deepCopy2函数,
const copy=DeepCopy 2(obj);console . log(copy . a);//输出:{ name : ' a ' } console . log(copy . d . e);//输出: { name : ' a ' } console . log(copy . a===copy . d . e);//输出:false从上面的性能可以看出,虽然opy.a和copy.d.e字面上是相等的,但它们引用的对象并不相同。在这一点上,对象副本和原始对象obj之间存在差异。
在这种情况下,因为obj.a不在obj.d.e的父对象链中,所以deepCopy2函数无法检测到obj.d.e也是obj.a的引用关系,因此deepCopy2函数将obj.a深度复制的结果分配给copy.d.e.
解决方法:父对象的引用是一种引用,非父对象的引用也是一种引用,只需要记录对象A中的所有对象,并使其与新创建的对象一一对应即可。
函数deepCopy3(obj) {//哈希表记录所有对象的引用关系let map=new WeakMap();函数DP(obj){ let result=null;让键=对象键(对象);let key=null,temp=null,existobj=nullexistobj=map . get(obj);//如果这个对象已经被记录,返回if(existobj){ return existobj;}结果={} map.set(obj,result);for(设i=0,len=keys.length伊琳;I){ key=keys[I];temp=obj[key];if(temp type of temp==' object '){ result[key]=DP(temp);} else { result[key]=temp;} }返回结果;}返回DP(obj);} const obj={ a : { name : ' a ' },b: { name: 'b' },c : { };c . d . e=obj . a;const copy=DeepCopy 3(obj);五.总结
其实复制的方式有很多,比如$。在jquery中扩展,_。在洛达什等地克隆人。复制中还有很多问题值得进一步研究,比如如何复制正则类型的值,如何复制原型上的属性,我会慢慢研究!也可以考虑一下~最后欢迎点赞收藏!欢迎纠正错误(`)
版权声明:对javascript深拷贝、浅拷贝和循环引用的深刻理解是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。