当前位置: 首页 > 后端技术 > Node.js

JavaScript系列: 一、手撕JS中的深浅拷贝

时间:2023-04-03 14:28:11 Node.js

JavaScript系列:1.手撕JS中的深浅拷贝。考生:JSON.parse(JSON.stringify(object))面试官:有什么优点和缺点?应聘者:函数不能序列化,忽略undefined……面试官:还有别的办法吗?如何解决循环引用?候选:for循环。..一、数据类型1、基本数据类型Number、String、Boolean、Null、undefined、Symbol、BigintBigint是新引入的基本数据类型2、引用数据类型Object、Array、Function等数据类型不是本文重点,重点在实现下面是要复制的对象,后面的代码会直接使用$obj,之后不再声明//lmranvar$obj={func:function(){console.log('这是函数')},date:newDate(),symbol:Symbol(),a:null,b:undefined,c:{a:1},e:newRegExp('regexp'),f:newError('错误')}$obj.c.d=$obj二、浅拷贝1、什么是浅拷贝?一句话,可以这么说,对于一个对象来说,如果它的一级属性值是基本数据类型,则完全复制一份数据,如果是引用类型,则复制内存地址。确实贝贝的很浅[偷笑]2、现实Object.assign()//lmranletobj1={name:'yang',res:{value:123}}letobj2=Object.assign({},obj1)obj2.res.value=456console.log(obj2)//{name:"haha",res:{value:456}}console.log(obj1)//{name:"haha",res:{value:456}}obj2.name='haha'console.log(obj2)//{name:"haha",res:{value:456}}console.log(obj1)//{name:"yang",res:{value:456}}展开语言法Spread//lmranletobj1={name:'yang',res:{value:123}}let{...obj2}=obj1obj2.res.value=456console.log(obj2)//{name:"haha",res:{value:456}}console.log(obj1)//{name:"haha",res:{value:456}}obj2.name='haha'console.log(obj2)//{name:"haha",res:{value:456}}console.log(obj1)//{name:"yang",res:{value:456}}Array.prototype.slice//lmranconstarr1=['杨',{值:123}];constarr2=arr1.slice(0);arr2[1].value=456;控制台日志(arr2);//["yang",{value:456}]console.log(arr1);//[“杨",{value:456}]arr2[0]='haha';console.log(arr2);//["haha",{value:456}]console.log(arr1);//["yang",{value:456}]Array.prototype.concat//lmranconstarr1=['yang',{value:123}];constarr2=[].concat(arr1);arr2[1].value=456;console.log(arr2);//["yang",{value:456}]console.log(arr1);//["yang",{value:456}]arr2[0]='haha';console.log(arr2);//["haha",{value:456}]console.log(arr1);//["yang",{value:456}]其实对于数组来说,只要原数组,返回一个新的数组实现浅拷贝,比如map,filter,reduce等方法3.深拷贝1.什么是深拷贝?没有共享数据的现象2.实现暴力版JSON.parse(JSON.stringify(object))//lmranletobj=JSON.parse(JSON.stringify($obj))console.log(obj)//无法解决循环Quote/*VM348:1UncaughtTypeError:ConvertingcircularstructuretoJSONatJSON.stringify()在:1:17*/delete$obj.c.dletobj=JSON.parse(JSON.stringify($obj))控制台。日志(对象)//大多数属性缺失/*{a:nullc:{a:1}date:"2020-04-05T09:51:32.610Z"e:{}f:{}}*/存在的问题:1,willignoreundefined2,会忽略symbol3,无法序列化函数4,无法解决循环引用对象5,无法正确处理newDate()6,无法处理正则表达式7,无法处理newError()初版基础版递归遍历Objectproperties//lmranfunctiondeepCopy(obj){if(obj===null||typeofobj!=='object'){returnobj}letcopy=Array.isArray(obj)?[]:{}Object.keys(obj).forEach(v=>{copy[key]=deepCopy(obj[key])})returncopy}deepCopy($obj)/*VM601:23UncaughtRangeError:最大调用堆栈大小超出:23:30atArray.forEach()atdeepCopy(:23:22)*/delete$obj.c.ddeepCopy($obj)/*{a:nullb:undefinedc:{a:1}date:{}e:{}f:{}func:?()symbol:Symbol()}*/存在的问题是:1.无法解析循环引用对象2.无法正确处理newDate()3,无法处理regular4,无法处理newError()解决循环引用的第二个版本首先解决循环遍历的问题,解决方法是将对象和对象属性存储在一个array在下次遍历时检查是否有已经遍历过的对象,如果有则直接返回,否则继续遍历//lmranfunctiondeepCopy(obj,cache=[]){if(obj===null||typeofobj!=='object'){returnobj}constitem=cache.filter(item=>item.original===obj)[0]if(item)returnitem.copyletcopy=Array.isArray(obj)?[]:{}cache.push({original:obj,copy})Object.keys(obj).forEach(key=>{copy[key]=deepCopy(obj[key],cache)})返回副本}deepCopy($obj)/*{a:nullb:undefinedc:{a:1,d:{...}}date:{}e:{}f:{}func:?()symbol:Symbol()}完美解决了循环引用问题,但是还有几个小问题,都属于同一类问题。第三个版本解决了特殊值对于final几个对象的处理,可以判断类型,renew一个返回即可。//lmranfunctiondeepCopy(obj,cache=[]){if(obj===null||typeofobj!=='object'){returnobj}if(Object.prototype.toString.call(obj)==='[objectDate]')returnnewDate(obj)if(Object.prototype.toString.call(obj)==='[objectRegExp]')returnnewRegExp(obj)if(Object.prototype.toString.call(对象)==='[对象错误]')returnnewError(obj)constitem=cache.filter(item=>item.original===obj)[0]if(item)returnitem.copyletcopy=Array.是数组(对象)?[]:{}cache.push({original:obj,copy})Object.keys(obj).forEach(key=>{copy[key]=deepCopy(obj[key],cache)})返回副本}deepCopy($obj)/*{a:nullb:undefinedc:{a:1,d:{...}}日期:2020年4月10日星期五20:06:08GMT+0800(中国标准时间){}e:/regexp/f:Error:Error:erroratdeepCopy(:8:74)at:19:21atArray.forEach()atdeepCopy(:18:22)at:24:1func:?()symbol:Symbol()}*/第四版解决同样的函数引用。到这里基本的功能好像已经实现了,但是还有一个问题,就是函数引用了同一个A内存地址,对于这个问题,网上大部分都是直接返回或者作为对象返回,包括lodash,它也处理constisFunc=typeofvalue=='function'if(isFunc||!cloneableTags[tag]){返回对象?value:{}}那么如何结束这个问题就需要用到eval函数了,虽然这个函数已经不推荐了,但它仍然可以解决问题这里有两种类型的函数:普通函数和箭头函数。区分两者,只需要看是否有样机即可。如果有原型属性,就是一个普通的函数。如果没有原型属性,它就是一个箭头函数。//lmranfunctioncopyFunction(func){让fnStr=func.toString()返回func.prototype?eval(`(${fnStr})`):eval(fnStr)}functiondeepCopy(obj,cache=[]){if(typeofobj==='function'){returncopyFunction(obj)}if(obj===null||typeofobj!=='object'){returnobj}if(Object.prototype.toString.call(obj)==='[objectDate]')returnnewDate(obj)if(Object.prototype.toString.call(obj)==='[objectRegExp]')returnnewRegExp(obj)if(Object.prototype.toString.call(obj)==='[objectError]')returnnewError(obj)constitem=cache.filter(item=>item.original===obj)[0]if(item)returnitem.copyletcopy=Array.isArray(obj)?[]:{}cache.push({original:obj,copy})Object.keys(obj).forEach(key=>{copy[key]=deepCopy(obj[key],cache)})返回副本}deepCopy($obj).func===$obj.func//false##至此,深浅复制已经完全实现,但是学无止境。我们也可以考虑使用Proxy通过拦截set和get实现来提高深拷贝性能,当然也可以使用Object.defineProperty()。有兴趣的同学可以看看这篇文章。文章对此进行了详细介绍。头条面试官:你知道如何实现高性能版本的深拷贝吗?针对@Brota提出的关于函数引用的相同问题,该文章现已修复文章中的任何问题。欢迎大家积极指出,非常感谢!!!参考MDNeval函数的使用Howtowriteadeepcopyofanamazinginterviewer?头条面试官:你知道如何实现高性能版本的深拷贝吗?