深克隆(deepcopy)一直是前端初中级面试中经常被问到的问题。这很容易理解;全面判断类型,根据类型做不同的处理;2的变体简化了类型判断过程。前两个比较常见,也比较基础,所以今天主要讨论第三个。问题分析深拷贝自然是相对于浅拷贝而言的。我们都知道引用数据类型变量存储的是对数据的引用,也就是指向内存空间的指针,所以如果我们像给简单数据类型赋值一样赋值,实际上只能复制一个指针引用而不能实现真实数据。克隆。通过这个例子很容易理解:constobj1={name:'superman'}constobj2=obj1;obj1.name='front-endcutout';console.log(obj2.name);//前端抠图所以深度克隆是为了解决引用数据类型无法通过赋值复制的问题。引用数据类型列举一下引用的数据类型:ES6之前:object,array,date,regularexpression,error,ES6之后:Map,Set,WeakMap,WeakSet,所以,如果要深度克隆,需要遍历数据和根据类型采取相应的克隆方法。当然,因为数据会有多层嵌套,递归是个不错的选择。简单粗暴版functiondeepClone(obj){letres={};//类型判断的通用方法functiongetType(obj){returnObject.prototype.toString.call(obj).replaceAll(newRegExp(/\[|\]|object/g),"");}常量类型=getType(obj);constreference=["Set","WeakSet","Map","WeakMap","RegExp","Date","Error"];if(type==="Object"){for(constkeyinobj){if(Object.hasOwnProperty.call(obj,key)){res[key]=deepClone(obj[key]);}}}elseif(type==="Array"){console.log('arrayobj',obj);obj.forEach((e,i)=>{res[i]=deepClone(e);});}elseif(type==="Date"){res=newDate(obj);}elseif(type==="RegExp"){res=newRegExp(obj);}elseif(type==="Map"){res=newMap(obj);}elseif(type==="Set"){res=newSet(obj);}elseif(type==="WeakMap"){res=newWeakMap(obj);}elseif(type==="WeakSet"){res=newWeakSet(obj);}elseif(type==="Error"){res=newError(obj);}else{res=obj;}returnres;}其实这就是我们上面提到的第二种方法。这很愚蠢,对吧?明眼人一看就知道有很多冗余代码可以合并。我们先进行最基本的优化:mergeredundantcodesto可以看出冗余代码被合并了。函数deepClone(obj){让res=null;//类型判断的通用方法functiongetType(obj){returnObject.prototype.toString.call(obj).replaceAll(newRegExp(/\[|\]|object/g),"");}常量类型=getType(obj);constreference=["Set","WeakSet","Map","WeakMap","RegExp","Date","Error"];if(type==="Object"){res={};for(constkeyinobj){if(Object.hasOwnProperty.call(obj,key)){res[key]=deepClone(obj[key]);}}}elseif(type==="Array"){console.log('arrayobj',obj);资源=[];obj.forEach((e,i)=>{res[i]=deepClone(e);});}//优化此部分剩余判断//elseif(type==="Date"){//res=newDate(obj);//}elseif(type==="RegExp"){//res=newRegExp(obj);//}elseif(type==="Map"){//res=newMap(obj);//}elseif(type==="Set"){//res=newSet(obj);//}elseif(type==="WeakMap"){//res=newWeakMap(obj);//}elseif(type==="WeakSet"){//res=newWeakSet(obj);//}elseif(type==="Error"){//res=newError(obj);//}elseif(reference.includes(type)){res=newobj.构造函数(对象);}else{res=obj;}returnres;}为了验证代码的正确性,我们使用如下数据进行验证:constmap=newMap();map.set("key","value");map.set("ConardLi","coder");constset=newSet();set.add("ConardLi");set.add("coder");consttarget={field1:1,field2:undefined,field3:{child:"child",},field4:[2,4,8],empty:null,map,set,bool:newBoolean(true),num:newNumber(2),str:newString(2),符号:Object(Symbol(1)),date:newDate(),reg:/\d+/,error:newError(),func1:()=>{让t=0;console.log("编码器",t++);},func2:函数(a,b){返回a+b;},};//测试代码consttest1=deepClone(target);target.field4.push(9);console.log('test1:',test1);执行结果:是否还有进一步优化的空间?答案当然是肯定的//判断类型的方法移到了外面,避免在递归时多次执行constjudgeType=origin=>{returnObject.prototype.toString.call(origin).replaceAll(newRegExp(/\[|\]|object/g),"");};constreference=["Set","WeakSet","Map","WeakMap","RegExp","Date","Error"];functiondeepClone(obj){//定义一个新的对象,最后返回//通过obj的原型创建一个对象constcloneObj=Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj));//遍历对象,克隆属性for(letkeyofReflect.ownKeys(obj)){constval=obj[key];consttype=judgeType(val);如果(reference.includes(type)){newObj[key]=newval.constructor(val);}elseif(typeofval==="object"&&val!==null){//递归克隆newObj[key]=deepClone(val);}else{//基本数据类型和函数newObj[key]=val;}}returnnewObj;}执行结果如下:Object.getOwnPropertyDescriptors()方法用于获取对象所有自身属性的描述符。返回指定对象的所有自有属性的描述符,如果没有自有属性,则返回空对象。这样做的好处是可以提前定义好最终返回的数据类型。这个实现参考了网上一个大佬的实现方法。个人觉得理解成本有点高,数组类型的处理不是特别优雅,返回一个类数组。我根据上面的代码做了一个改造,改造后的代码如下:functiondeepClone(obj){letres=null;constreference=[Date,RegExp,Set,WeakSet,Map,WeakMap,Error];如果(reference.includes(obj?.constructor)){res=newobj.constructor(obj);}elseif(Array.isArray(obj)){res=[];obj.forEach((e,i)=>{res[i]=deepClone(e);});}elseif(typeofobj==="Object"&&obj!==null){res={};for(constkeyinobj){if(Object.hasOwnProperty.call(obj,key)){res[key]=deepClone(obj[key]);}}}else{res=obj;}returnres;}虽然在代码量上没有优势,但是整体的理解成本我觉得随着你的清晰度会更好。所以你怎么看?最后就是循环引用的问题,避免死循环的问题。我们使用hash来存储已经加载过的对象,如果已经存在则直接返回。函数deepClone(obj,hash=newWeakMap()){if(hash.has(obj)){returnobj;}让res=null;constreference=[Date,RegExp,Set,WeakSet,Map,WeakMap,Error];如果(reference.includes(obj?.constructor)){res=newobj.constructor(obj);}elseif(Array.isArray(obj)){res=[];obj.forEach((e,i)=>{res[i]=deepClone(e);});}elseif(typeofobj==="Object"&&obj!==null){res={};for(constkeyinobj){if(Object.hasOwnProperty.call(obj,key)){res[key]=deepClone(obj[key]);}}}else{res=obj;}hash.set(obj,res);returnres;}总结对于深拷贝的实现,可能有很多种不同的实现方式。关键是理解它的原理,记住最容易理解和实现的方法。只有面对类似的问题,我们才能淡定从容。
