前言大家好,我是林三鑫。前几天和领导聊深拷贝领导:你知道怎么复制一个对象吗?我知道!不就是深拷贝吗?leader:那你是怎么做深拷贝的?我:我只是在世界各地吃JSON.parse(JSON.stringfy(obj))。Leader:大哥,有时间的话去lodash里面的deepClone看看是怎么实现的。哈哈,的确,深拷贝在日常开发中的应用场景很多,他也很重要。写一个合格的深拷贝方法是很有必要的。那么如何才能写出合格的深拷贝方法呢?换句话说,我们如何才能写出完美的深拷贝方法呢?Deepcopy&&shallowcopy先来说说什么是深拷贝,什么是浅拷贝。浅拷贝所谓浅拷贝就是只拷贝最外层,里面的引用还是一样//浅拷贝consta={name:'sunshine_lin',age:23,arr:[]}constb={}for(letkeyina){b[key]=a[key]}console.log(b)//{name:'sunshine_lin',age:23,arr:[]}console.log(b===a)//falseconsole.log(b.arr===a.arr)//truedeepcopydeepcopy的意思是你把一个对象复制到另一个新的变量中,这个新的变量指向一个新的堆内存地址//深拷贝函数deepClone(target){//...实现深拷贝}consta={name:'sunshine_lin',age:23,arr:[]}constb=deepClone(a)console.log(b)//{name:'sunshine_lin',age:23,arr:[]}console.log(b===a)//falseconsole.log(b.arr===a.arr)//假金版相信大多数人在实现深拷贝的时候,都会实现functiondeepClone(target){returnJSON.parse(JSON.stringify(target))}consta={name:'sunshine_lin',age:23}constb=deepClone(a)控制台日志(b)//{name:'sunshine_lin',age:23}console.log(b===a)//false虽然大部分时候用这种方式没问题,但是这种方式还是有很多缺点。1、如果对象中有字段值未定义,转换后该字段会直接消失。2.如果对象有字段值是RegExp对象,转换后字段值会变成{}。3.如果对象有一个字段值为NaN,+-Infinity。转换后,该字段值变为null4。如果对象有环引用,转换会直接报错,因为白金版要正确。如果你想做一个对象的深拷贝,那我可以创建一个空对象,把原对象需要拷贝的值一个一个拷贝过来!!!functiondeepClone(target){consttemp={}for(constkeyintarget){temp[key]=target[key]}returntemp}consta={name:'sunshine_lin',age:23}constb=deepClone(a)console.log(b)//{name:'sunshine_lin',age:23}console.log(b===a)//false但其实上面的方法并不完美,因为我们不't知道我们要复制的对象有多少层。当你听到“我不知道有多少层”时,你一定会想到递归。是的,你可以使用递归。functiondeepClone(target){//基本数据类型直接返回if(typeoftarget!=='object'){returntarget}//引用数据类型特殊处理consttemp={}for(constkeyintarget){//recursivetemp[key]=deepClone(target[key])}returntemp}consta={name:'sunshine_lin',age:23,hobbies:{sports:'basketball',tv:'雍正王朝'}}constb=deepClone(a)console.log(b)//{//name:'sunshine_lin',//age:23,//hobbies:{sports:'basketball',tv:'雍正王朝'}//}console.log(b===a)//false在钻石版中,我们只考虑了对象情况,没有考虑数组情况,所以需要添加数组条件functiondeepClone(target){//基础datatype直接返回if(typeoftarget!=='object'){returntarget}//引用数据类型特殊处理//判断数组还是对象consttemp=Array.isArray(target)?[]:{}for(constkeyintarget){//递归temp[key]=deepClone(target[key])}returntemp}consta={name:'sunshine_lin',age:23,hobbies:{运动:'basketball',tv:'雍正王朝'},works:['2020','2021']}constb=deepClone(a)console.log(b)//{//name:'sunshine_lin',//age:23,//hobbies:{sports:'basketball',tv:'雍正王朝'},//works:['2020','2021']//}console.log(b===a)//false之前星耀版本实现的方法都没有解决环引用的问题JSON.parse(JSON.stringify(target))报错TypeError:ConvertingcircularstructuretoJSON,意思是环引用的递归方法无法处理,报错Maximumcallstacksizeexceeded,意思是递归无休止,爆栈//ringreferenceconsta={}a.key=athen如何解决ringreference?其实不难说,每次遍历都需要用到ES6的数据结构Map,有一个引用数据类型,作为key放入Map中,对应的value就是每次新创建的对象temp你遍历到有reference数据类型,去Map里找有没有对应的key,如果有,说明这个对象之前注册过,现在是第二次,肯定是环引用,直接根据key取值,返回值functiondeepClone(target,map=newMap()){//基本数据类型直接返回if(typeoftarget!=='object'){returntarget}//特殊handlingforreferencedatatypes//判断是数组还是对象consttemp=Array.isArray(target)?[]:{}+if(map.get(target)){+//如果存在则直接返回+returnmap.get(target)+}+//如果不存在则为firsttimeset+map.set(target,temp)for(constkeyintarget){//递归temp[key]=deepClone(target[key],map)}returntemp}consta={name:'sunshine_lin',age:23,hobbies:{sports:'basketball',tv:'雍正王朝'},works:['2020','2021']}a.key=a//ringreferenceconstb=deepClone(a)console.log(b)//{//name:'sunshine_lin',//age:23,//hobbies:{运动:'Basketball',tv:'雍正王朝'},//works:['2020','2021'],//key:[Circular]//}console.log(b===a)//false王者版我们只是实现了基本数据类型、引用数据类型中的数组、对象的拷贝,但实际上引用数据类型不仅仅是数组和对象,我们还要解决下面的拷贝问题引用类型,那么如何判断每个引用数据类型各自的类型呢?您可以使用Object.prototype.toString.call()typetoStringresultMapObject.prototype.toString.call(newMap())[objectMap]SetObject.prototype.toString.call(newSet())[objectSet]ArrayObject.prototype.toString.call([])[objectArray]ObjectObject.prototype.toString.call({})[objectObject]SymbolObject.prototype.toString.call(Symbol())[objectSymbol]RegExpObject.prototype.toString。call(newRegExp())[objectRegExp]FunctionObject.prototype.toString.call(function(){})[objectFunction]我们先把上面的引用类型编号分为可遍历数据类型和不可遍历数据类型两种//可遍历类型constmapTag='[objectMap]';constsetTag='[对象集]';constarrayTag='[objectArray]';constobjectTag='[objectObject]';//不可遍历类型constsymbolTag='[objectSymbol]';constregexpTag='[objectRegExp]';constfuncTag='[objectFunction]';//将可用遍历类型存在于数组中constcanForArr=['[objectMap]','[objectSet]','[objectArray]','[objectObject]']//数组中存在不可遍历类型constnoForArr=['[objectSymbol]','[objectRegExp]','[objectFunction]']//判断类型functionfunctioncheckType(target){returnObject.prototype.toString.call(target)}//判断引用类型tempfunctioncheckTemp(target){constc=target.constructorreturnnewc()}可遍历引用类型主要处理以下四种类型MapSetObjectArrayfunctiondeepClone(target,map=newMap()){//获取类型consttype=checkType(target)//直接返回基本数据类型if(!canForArr.concat(noForArr).includes(type)){returntarget}//特殊处理参考数据类型consttemp=checkTemp(target)if(map.get(target)){//如果存在,直接返回returnmap.get(target)}//如果不存在,第一次设置map.set(target,temp)//处理Map类型if(type===mapTag){target.forEach((value,key)=>{temp.set(key,deepClone(value,map))})returntemp}//处理Set类型if(type===setTag){target.forEach(value=>{temp.add(deepClone(value,map))})returntemp}//处理数据和对象for(constkeyintarget){//递归temp[key]=deepClone(target[key],map)}returntemp}consta={name:'sunshine_lin',age:23,hobbies:{sports:'basketball',tv:'YongzhengDynasty'},works:['2020','2021'],map:newMap([['haha',111],['xixi',222]]),set:newSet([1,2,3]),}a.key=a//环引用constb=deepClone(a)console.log(b)//{//name:'sunshine_lin',//age:23,//hobbies:{sports:'basketball',tv:'YongzhengDynasty'},//works:['2020','2021'],//map:Map{'haha'=>111,'xixi'=>222},//set:Set{1,2,3},//key:[Circular]//}安慰e.log(b===a)//false不可遍历引用类型主要处理以下类型SymbolRegExpFunction先写出复制这三种类型的方法/(?<={)(.|\n)+(?=})/m;constparamReg=/(?<=\().+(?=\)\s+{)/;constfuncString=func.toString();if(func.prototype){constparam=paramReg.exec(funcString);constbody=bodyReg.exec(funcString);if(body){if(param){constparamArr=param[0].分裂(',');返回新函数(...paramArr,正文[0]);}else{returnnewFunction(body[0]);}}else{返回空值;}}else{返回eval(funcString);}}//复制Symbol的方法functioncloneSymbol(target){returnObject(Symbol.prototype.valueOf.call(target));}//复制RegExp的方法functioncloneReg(target){constreFlags=/\w*$/;constresult=newtarget.constructor(target.source,reFlags.exec(target));结果.lastIndex=target.lastIndex;returnresult;}finalversionfunctiondeepClone(target,map=newMap()){//获取类型consttype=checkType(target)//基本数据类型直接返回if(!canForArr.concat(noForArr).includes(type))returntarget//判断Function,RegExp,Symbol+if(type===funcTag)returncloneFunction(target)+if(type===regexpTag)returncloneReg(target)+if(type===symbolTag)returncloneSymbol(target)//引用数据类型特殊处理consttemp=checkTemp(target)if(map.get(target)){//如果已经存在,直接返回returnmap.get(target)}//如果是不存在,第一次设置map.set(target,temp)//处理Map类型if(type===mapTag){target.forEach((value,key)=>{temp.set(key,deepClone(value,map))})returntemp}//处理集合类型if(type===setTag){target.forEach(value=>{temp.add(deepClone(value,map))})returntemp}//处理数据和对象(constkeyintarget){//递归temp[key]=deepClone(target[key],map)}returntemp}consta={name:'sunshine_lin',age:23,hobbies:{sports:'basketball',tv:'雍正王朝'},works:['2020','2021'],map:newMap([['haha',111],['xixi',222]]),set:newSet([1,2,3]),func:(name,age)=>`${name}今年是${age}!!!`,sym:Symbol(123),reg:newRegExp(/haha/g),}a.key=a//环引用constb=deepClone(a)console.log(b)//{//name:'sunshine_lin',//年龄:23,//爱好:{sports:'篮球',tv:'雍正王朝'},//作品:['2020','2021'],//map:地图{'haha'=>111,'xixi'=>222},//set:Set{1,2,3},//func:[函数],//sym:[Symbol:Symbol(123)],//reg:/haha/g,//key:[Circular]//}console.log(b===a)//false结论如果您觉得本文对您有帮助,请点赞鼓励林三新哈哈或可以加入我的摸鱼群,一起努力学习,我会定期模拟面试,简历指导,答疑解惑,互相学习,共同进步!!
