当前位置: 首页 > Web前端 > HTML

浅拷贝vs深拷贝

时间:2023-04-02 15:57:11 HTML

前言在javascript中拷贝对象的方式多种多样,如果不熟悉这门语言,在拷贝对象的时候很容易掉入陷阱,那么我们该如何正确的拷贝一个对象呢?目的?看完这篇文章,希望你能明白:什么是深拷贝/浅拷贝,它们和赋值有什么区别?有多少深/浅拷贝的实现?浅拷贝和深拷贝浅拷贝创建一个新对象,该对象具有原始对象属性值的精确副本。如果属性是基本类型,则复制基本类型的值。如果属性是引用类型,则内存地址被复制,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。深拷贝就是从内存中对一个对象进行完整的拷贝,从堆内存中开辟一块新的区域来存放新的对象,在不影响原对象的情况下修改新的对象。vara1={b:{c:{}};vara2=shallowClone(a1);//浅拷贝方法a2.b.c===a1.b.c//真正的新旧对象仍然共享相同的内存vara3=deepClone(a3);//深拷贝方法a3.b.c===a1.b.c//false新对象不与原对象共享内存。借助下面ConardLi老师的两张图,我们可以更好的理解两者的含义:简而言之,Shallowcopy只复制对象的指针,不复制对象本身,新旧对象仍然共享同样的记忆。但是,深拷贝将创建一个相同的对象。新对象不与原对象共享内存,修改新对象不会改变原对象。赋值和深/浅拷贝的区别三者的区别如下,但是比较的前提是引用类型:当我们将一个对象赋值给一个新的变量时,实际赋值的是对象的地址在堆栈中,而不是堆中的数据。即两个对象指向同一个存储空间。无论哪个对象发生变化,实际上都是变化的存储空间的内容。因此,这两个对象是链接在一起的。浅拷贝:在堆中重新创建内存。复制前后对象的基本数据类型互不影响,但复制前后对象的引用类型会相互影响,因为它们共享同一块内存。深拷贝:从堆内存中开辟一块新的区域存放新的对象,递归拷贝对象中的子对象,拷贝前后两个对象互不影响。先看下面的例子,比较赋值和深/浅复制对修改后的原始对象的影响://objectassignmentletobj1={name:'boatinginthewaves',arr:[1,[2,3],4],};letobj2=obj1;obj2.name="阿郎";obj2.arr[1]=[5,6,7];console.log('obj1',obj1)//obj1{name:'Alang',arr:[1,[5,6,7],4]}console.log('obj2',obj2)//obj2{name:'Alang',arr:[1,[5,6,7],4]}//浅拷贝letobj1={name:'boatinthewaves',arr:[1,[2,3],4],};letobj3=shallowClone(obj1)obj3.name="阿郎";obj3.arr[1]=[5,6,7];//旧对象和新对象仍然共享相同的内存//这是一个浅拷贝方法functionshallowClone(source){vartarget={};for(variinsource){if(source.hasOwnProperty(i)){target[i]=source[i];}}returntarget;}console.log('obj1',obj1)//obj1{name:'浪中之舟',arr:[1,[5,6,7],4]}console.log('obj3',obj3)//obj3{name:'Awave',arr:[1,[5,6,7],4]}//深拷贝letobj1={name:'在海浪中划船',arr:[1,[2,3],4],};letobj4=deepClone(obj1)obj4.name="Alang";obj4.arr[1]=[5,6,7];//新对象不与原对象共享内存//这是一个深拷贝方法functiondeep克隆(obj){如果(obj===null)返回obj;如果(objinstanceofDate)返回新日期(obj);if(objinstanceofRegExp)returnnewRegExp(obj);if(typeofobj!=="object")返回obj;让cloneObj=newobj.constructor();for(letkeyinobj){if(obj.hasOwnProperty(key)){//实现递归复制cloneObj[key]=deepClone(obj[key]);}}returncloneObj;}console.log('obj1',obj1)//obj1{name:'乘船在海浪',arr:[1,[2,3],4]}console.log('obj4',obj4)//obj4{name:'Alang',arr:[1,[5,6,7],4]}上例中obj1为原始对象,obj2为赋值操作得到的对象.obj3浅拷贝得到的对象和obj4深拷贝得到的对象,通过下表我们可以清楚的看出它们对原始数据的影响:浅拷贝的实现方法1.Object.assign()Object.assign()方法可以将源对象本身的任意数量的可枚举属性复制到目标对象,然后返回目标对象letobj1={person:{name:"kobe",age:41},sports:'basketball'};letobj2=Object.assign({},obj1);obj2.person.name="wade";obj2.sports='football'console.log(obj1);//{person:{name:'wade',age:41},sports:'basketball'}2.函数库lodash也提供了_.clone方法,函数库_.clone用于ShallowCopy,后面会介绍使用这个库实现深拷贝。var_=require('lodash');varobj1={a:1,b:{f:{g:1}},c:[1,2,3]};varobj2=_.clone(obj1);console.log(obj1.b.f===obj2.b.f);//true3.Spreadoperator...spreadoperator是es6/es2015的一个特性,它提供了一种非常方便的方式来执行浅拷贝,这与Object.assign()的功能相同。letobj1={name:'Kobe',address:{x:100,y:100}}letobj2={...obj1}obj1.address.x=200;obj1.name='wade'console.log('obj2',obj2)//obj2{name:'Kobe',address:{x:200,y:100}}4.Array.prototype.concat()letarr=[1,3,{username:'kobe'}];让arr2=arr.concat();arr2[2].username='wade';console.log(arr);//[1,3,{username:'wade'}]5.数组。prototype.slice()letarr=[1,3,{username:'kobe'}];letarr3=arr.slice();arr3[2].username='wade'console.log(arr);//[1,3,{username:'wade'}]深拷贝的实现1.JSON.parse(JSON.stringify())letarr=[1,3,{username:'kobe'}];letarr4=JSON.parse(JSON.stringify(arr));arr4[2].username='duncan';console.log(arr,arr4)这个也是用JSON.stringify把对象转成JSON字符串,然后用JSON.parse把字符串解析成对象,生成一个新的对象,打开对象一个新的栈来实现深拷贝。这种方法虽然可以实现数组或者对象的深拷贝,但是不能处理函数和正则表达式,因为这两者是基于JSON处理后的。该函数不再是函数(变为空)。例如下面的例子:letarr=[1,3,{username:'kobe'},function(){}];letarr4=JSON.parse(JSON.stringify(arr));arr4[2]。用户名='邓肯';console.log(arr,arr4)2.函数库lodash的_.cloneDeep方法函数库也提供了DeepCopy的_.cloneDeepvar_=require('lodash');varobj1={a:1,b:{f:{g:1}},c:[1,2,3]};varobj2=_.cloneDeep(obj1);console.log(obj1.b.f===obj2.b.f);//false3.jQuery.extend()方法jquery提供了一个$.extend可以用于深拷贝$.extend(deepCopy,target,object1,[objectN])//第一个参数为true,为深拷贝var$=require('jquery');varobj1={a:1,b:{f:{g:1}},c:[1,2,3]};varobj2=$.extend(true,{},obj1);控制台日志(obj1.b.f===obj2.b.f);//错误4。手写递归法递归法实现了深克隆的原理:遍历对象和数组直到都是基本数据类型,然后复制,也就是深拷贝。有一个特殊情况需要注意,对象存在循环引用,即对象的属性直接引用自身。为了解决循环引用问题,我们可以开辟一个额外的存储空间来存储当前对象和复制对象的对应关系。当需要复制当前对象时,先到存储空间中查找该对象是否被复制过,如果有则直接返回,如果没有则继续复制,这样就可以解决循环引用的问题巧妙地。如果对此有疑惑,请仔细阅读李康是如何写出深度文案的惊艳面试官?本文。函数deepClone(obj,hash=newWeakMap()){if(obj===null)returnobj;//如果是null或者undefined,我就不执行复制操作if(objinstanceofDate)returnnewDate(obj);if(objinstanceofRegExp)returnnewRegExp(obj);//可以是对象也可以是普通值,如果是函数则不需要深拷贝if(typeofobj!=="object")returnobj;//如果是对象需要深拷贝if(hash.get(obj))returnhash.get(obj);让cloneObj=newobj.constructor();//找到类原型上的构造函数,原型上的构造函数指向当前类本身是什么hash.set(obj,cloneObj);for(letkeyinobj){if(obj.hasOwnProperty(key)){//实现递归复制cloneObj[key]=deepClone(obj[key],hash);}}returncloneObj;}letobj={name:1,address:{x:100}};obj.o=对象;//存在对对象的循环引用letd=deepClone(obj);obj.地址.x=200;console.log(d);欢迎关注公众号:前端工匠,让我们一起见证你的成长!参考文章如何写出深度文案,惊艳面试官?JavaScript浅拷贝与深拷贝js深拷贝vs浅拷贝深拷贝的终极探索(99%的人不知道)如何深度克隆一个JavaScript对象