当前位置: 首页 > 科技观察

一篇文章看懂Javascript深拷贝和浅拷贝

时间:2023-03-11 22:57:07 科技观察

前言javascript中复制对象的方式有很多种。如果不熟悉这门语言,在复制对象时很容易掉入陷阱。那么我们怎样才能正确的复制一个对象呢?看完这篇文章,希望你能明白:什么是深拷贝/浅拷贝,它们和赋值有什么区别?深/浅拷贝有多少种实现方式?浅拷贝和深拷贝具有原始对象属性值的精确副本的新对象。如果属性是基本类型,则复制基本类型的值。如果属性是引用类型,则内存地址被复制,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。深拷贝就是从内存中对一个对象进行完整的拷贝,从堆内存中开辟一块新的区域来存放新的对象,在不影响原对象的情况下修改新的对象。vara1={b:{c:{}};vara2=shallowClone(a1);//浅拷贝方式a2.b.c===a1.b.c//真正的新老对象仍然共享内存vara3=deepClone(a3);//深拷贝方法a3.b.c===a1.b.c//false新对象不与原对象共享内存。借助下面ConardLi老师的两张图,我们可以更好的理解两者的含义:总而言之,浅拷贝只是复制一个指向对象的指针,而不是对象本身,新旧对象仍然共享同样的记忆。但是,深拷贝将创建一个相同的对象。新对象不与原对象共享内存,修改新对象不会改变原对象。赋值和深/浅拷贝的区别三者的区别如下,但是比较的前提是引用类型:当我们将一个对象赋值给一个新的变量时,实际赋值的是对象的地址在堆栈中,而不是堆中的数据。即两个对象指向同一个存储空间。无论哪个对象发生变化,实际上都是变化的存储空间的内容。因此,这两个对象是链接在一起的。浅拷贝:在堆中重新创建内存。复制前后对象的基本数据类型互不影响,但复制前后对象的引用类型会相互影响,因为它们共享同一块内存。深拷贝:从堆内存中开辟一块新的区域存放新的对象,递归拷贝对象中的子对象,拷贝前后两个对象互不影响。我们先看下面的例子,比较赋值和深/浅拷贝获取对象后对象修改对原对象的影响:1,[2,3],4],};letobj2=obj1;obj2.name="Alang";obj2.arr[1]=[5,6,7];console.log('obj1',obj1)//obj1{name:'阿郎',arr:[1,[5,6,7],4]}console.log('obj2',obj2)//obj2{name:'阿郎',arr:[1,[5,6,7],4]}//浅拷贝letobj1={name:'浪中之舟',arr:[1,[2,3],4],};letobj3=shallowClone(obj1)obj3。name="Alang";obj3.arr[1]=[5,6,7];//新旧对象仍然共享内存//这是一种浅拷贝方法functionsshallowClone(source){vartarget={};for(variinsource){if(source.hasOwnProperty(i)){target[i]=source[i];}}returntarget;}console.log('obj1',obj1)//obj1{name:'划船在waves',arr:[1,[5,6,7],4]}console.log('obj3',obj3)//obj3{name:'阿郎',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];//新对象不与原对象共享内存//这是一个深拷贝方法functiondeepClone(obj){if(obj===null)返回对象;如果(objinstanceofDate)returnnewDate(obj);if(objinstanceofRegExp)returnnewRegExp(obj);if(typeofobj!==“对象”)returnobj;让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就是深拷贝Objects得到的对象,通过下表我们可以清楚的看出它们对原始数据的影响:浅拷贝实现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);控制台。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'}];letarr2=arr.concat();arr2[2].username='wade';console.log(arr);//[1,3,{username:'wade'}]5.Array.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].username='duncan';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);安慰。log(obj1.b.f===obj2.b.f);//false4.手写递归法递归法实现了深克隆的原理:遍历对象和数组,直到都是基本数据类型,然后复制,这就是深复制。有一个特殊情况需要注意,对象存在循环引用,即对象的属性直接引用自身。为了解决循环引用问题,我们可以开辟一个额外的存储空间来存储当前对象和复制对象的对应关系。当需要复制当前对象时,先到存储空间中查找该对象是否被复制过,如果有则直接返回,如果没有则继续复制,这样就可以解决循环引用的问题巧妙地。如果对此有疑惑,请仔细阅读李康是如何写出深度文案的惊艳面试官?本文。functiondeepClone(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);letcloneObj=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=obj;//对象有循环引用letd=deepClone(obj);obj.address.x=200;console.log(d);