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

超全面的自定义深拷贝功能

时间:2023-03-28 12:51:15 HTML

关于直接赋值、浅拷贝、深拷贝的区别和示意图,在之前json的文章中有介绍。你可以参考这篇文章。json虽然简单,但是这些细节你可能不知道。复制第一层首先,我们定义最简单的浅拷贝,只要能保存原对象的第一层数据即可~functiondeepClone(value){constnewObj={};//遍历原对象,赋值给新对象Intheobjectfor(letkeyinvalue){newObj[key]=value[key];}returnnewObj;}//定义一个对象constobj={name:"alice",friend:{name:"windy",},};//复制的新对象constuser=deepClone(obj);user.name="kiki";user.friend.name="kiki";console.log("原始对象obj-",obj);console.log("newobjectuser-",user)修改新对象第一层属性name的值时,不会修改原对象,而是修改新对象中第二层属性friend.name的值object会被windy改变当修改为kiki时,原来的object也会改变。递归处理可能仍然存在于对象的属性中。只有一层判断是不够的,需要递归处理。当递归值不是对象时,直接return//判断传入的数据是否是对象functionisObject(obj){//函数也是对象returnobj!==null&&(typeofobj==="function"||typeofobj==="object");}functiondeepClone(value){//当value不是对象时,直接返回valueif(!isObject(value))returnvalue;constnewObj={};对于(让keyinvalue){//递归处理valuenewObj[key]=deepClone(value[key]);}returnnewObj;}constobj={name:"alice",friend:{name:"windy",address:{city:"Beijing",},},hobbies:["游泳","跳舞","网球""],};constuser=deepClone(obj);user.friend.name="kiki";user.friend.address.city='上海';console.log("原始对象obj-",obj);console.log("Newobjectuser-",user)递归调用后,复制的对象全部开辟一块新的内存空间来保存,所以修改新对象的值,原来的对象不会改变但是我们发现一个新问题,原来对象中的hobbies属性值类型是“array”,复制到新对象中就变成了“object”。Arrays所以要区分数组和对象的处理,对传入的数据进行判断。如果是数组,复制创建的数据类型应该是数组//判断传入的数据是否是对象functionisObject(obj){returnobj!==null&&(typeofobj==="function"||typeofobj==="object");}functiondeepClone(value){//当value不是对象时,直接返回valueif(!isObject(value))returnvalue;//判断value是否为数组,如果是数组则新建一个数组constnewObj=Array.isArray(value)?[]:{};for(letkeyinvalue){//递归处理值newObj[key]=deepClone(value[key]);}returnnewObj;}constobj={name:"alice",friend:{name:"windy",address:{city:"Beijing",},},hobbies:["游泳","跳舞","网球""],styding(){return"我在读书~";},};constuser=deepClone(obj);console.log("原始对象obj-",obj);console.log("Newobjectuser-",user)这时候对象和数组都可以正确复制,但是我们在原对象上加了一个“学习方法”,而复制的新对象的“方法”中间变成了一个“空对象”。方法函数本身就是为了复用,所以当对象中的方法被深拷贝时,可以复用原来的方法,不需要新建,直接返回原来的方法即可//判断传入数据是否为对象functionisObject(obj){returnobj!==null&&(typeofobj==="function"||typeofobj==="object");}functiondeepClone(value){//当值为函数时,直接返回原函数if(typeofvalue==="function")returnvalue;//当值不是对象时,直接返回值if(!isObject(value))returnvalue;//判断value是否为数组,如果是数组则新建一个数组constnewObj=Array.isArray(value)?[]:{};for(letkeyinvalue){//递归处理值newObj[key]=deepClone(value[key]);}returnnewObj;}constsymbolKey=Symbol("symbolKey");constsymbolValue=Symbol("symbolValue");constobj={name:"alice",friend:{name:"windy",address:{城市:"北京",},},hobbies:["游泳","跳舞","网球"],styding(){return"我在读书~";},[symbolKey]:"key",value:symbolValue,};constuser=deepClone(obj);console.log("原始对象obj-",obj);console.log("Newobjectuser-",user)原对象和新对象的方法指向同一个内存地址,但是我们一般不需要对方法进行修改,将符号作为key和value添加到原始对象。当使用symbol属性作为key时,直接在new对象中丢失Symbol作为key也是很常见的。为避免键值重复,为了获取符号的所有值,需要通过getOwnPropertySymbols并添加到新对象中//判断传入数据是否为对象functionisObject(obj){returnobj!==null&&(typeofobj==="function"||typeofobj==="object");}functiondeepClone(value){//当值为一个符号时,返回一个新的符号if(typeofvalue==="symbol")returnSymbol(value.description);//当value为函数时,直接返回原函数if(typeofvalue==="function")returnvalue;//当value不是对象时,直接返回valueif(!isObject(value))returnvalue;//判断value是否为数组,如果是数组则新建一个数组constnewObj=Array.isArray(value)?[]:{};for(letkeyinvalue){//递归处理值newObj[key]=deepClone(value[key]);}//获取符号为key的所有数据constsymbols=Object.getOwnPropertySymbols(value);for(letsymofsymbols){newObj[sym]=deepClone(value[sym]);}returnnewObj;}constsymbolKey=Symbol("symbolKey");constsymbolValue=Symbol("symbolValue");constset=newSet([1,2,3,4,5]);constmap=newMap([["名称","alice"],["age",20],]);constobj={name:"alice",friend:{name:"windy",address:{city:"北京",},},hobbies:["游泳","跳舞","网球"],styding(){return"我在读书~";},[symbolKey]:"key",value:symbolValue,set,map,};constuser=deepClone(obj);console.log("Originalobjectobj-",obj);console.log("Newobjectuser-",user);symbol属性无论作为key还是value都可以复制成功,但是新的object被copy了,map和set的值变成了“空对象”,map和set作为值的场景比较少,所以这里直接使用浅拷贝,当value为map/set时,创建了一个新的地图/集合。并返回//判断传入数据是否为对象functionisObject(obj){returnobj!==null&&(typeofobj==="function"||typeofobj==="object");}functiondeepClone(value){//当设置值时,返回一个新的集合if(valueinstanceofSet)returnnewSet([...value]);//当value为map时,返回一个新的mapif(valueinstanceofMap)returnnewMap([...value]);//当value为symbol时,返回一个新的symbolif(typeofvalue==="symbol")returnSymbol(value.description);//当value为函数时,直接返回原函数if(typeofvalue==="function")returnvalue;//当值不是对象时,直接返回值if(!isObject(value))returnvalue;//判断值是否为数组,为数组新建一个数组constnewObj=Array.isArray(value)?[]:{};for(letkeyinvalue){//递归处理值newObj[key]=deepClone(value[key]);}//获取符号为key的所有数据constsymbols=Object.getOwnPropertySymbols(value);for(letsymofsymbols){newObj[sym]=deepClone(value[sym]);}returnnewObj;}constsymbolKey=Symbol("符号键");constsymbolValue=Symbol("symbolValue");constset=newSet([1,2,3,4,5]);constmap=newMap([["姓名","爱丽丝"],["年龄",20],]);constobj={name:"alice",friend:{name:"windy",address:{city:"Beijing",},},hobbies:["游泳","跳舞","网球"],styding(){return"我在读书~";},[symbolKey]:"key",value:symbolValue,set,map,};constuser=deepClone(obj);console.log("原始对象obj-",obj);console.log("新对象user-",用户);map和set也成功复制到新对象。循环引用对象可能存在循环引用,窗口中存在window属性,如果要复制的对象也有指向自身的属性,如obj.info=obj,则可以连续调用。我们上面的深拷贝函数会死循环,报RangeError栈溢出错误。因此,需要优化深拷贝的代码,定义一个map/weakMap来保存传入的值和新建对象的值,并且每次调用该函数时,先判断传入的值是否已经被复制copied,if复制后,直接返回之前复制的值,避免死循环。//判断传入数据是否为对象functionisObject(obj){returnobj!==null&&(typeofobj==="function"||typeofobj==="object");}functiondeepClone(value,map=newWeakMap()){//当设置值时,返回一个新的集合if(valueinstanceofSet)returnnewSet([...value]);//当value为map时,返回一个新的mapif(valueinstanceofMap)returnnewMap([...value]);//当value为symbol时,返回一个新的symbolif(typeofvalue==="symbol")returnSymbol(value.description);//当value为函数时,直接返回原函数if(typeofvalue==="function")returnvalue;//当值不是对象时,直接返回值if(!isObject(value))returnvalue;//当map中有value时,直接返回map中的valueif(map.has(value)){returnmap.get(value);}//判断value是否为数组,如果是数组则新建一个数组constnewObj=Array.isArray(value)?[]:{};//将函数接收到的值和新建的obj保存到map中map.set(value,newObj);for(letkeyinvalue){//递归处理值newObj[key]=deepClone(value[key],map);}//获取符号keyconstsymbols=Object.getOwnPropertySymbols(value)的所有数据;for(letsymofsymbols){newObj[sym]=deepClone(value[sym],map);}returnnewObj;}constsymbolKey=Symbol("symbolKey");constsymbolValue=Symbol("symbolValue");constset=newSet([1,2,3,4,5]);constmap=newMap([["name","alice"],["age",20],]);constobj={name:"alice",friend:{name:"windy",address:{city:"北京",},},hobbies:["游泳","跳舞","网球"],styding(){return"我在读书~";},[symbolKey]:"key",value:symbolValue,set,map,};obj.info=obj;constuser=deepClone(obj);console.log("原始对象obj-",obj);console.log("新建对象用户-",user);以上就完成了自定义深拷贝的所有步骤,在使用深拷贝的时候,不用担心修改一个变量,拷贝得到的另一个变量也随之改变,可以有效降低代码的bug率。关于js进阶,开发者需要掌握的东西还是很多的。可以看我写的其他博文,持续更新~