1.JS中浅拷贝的手段有哪些?1、什么是文案?让我们看下面的例子来帮助你区分赋值和复制的区别:letarr=[1,2,3];letnewArr=arr;newArr[0]=100;console.log(arr);//[100,2,3]这是直接赋值的情况,不涉及任何复制。改变newArr时,由于是同一个引用,所以arr指向的值也随之改变。现在做一个浅拷贝:letarr=[1,2,3];letnewArr=arr.slice();newArr[0]=100;console.log(arr);//[1,2,3]当我们修改的时候newArr,arr的值没有变化,这是因为newArr是arr浅拷贝的结果,newArr和arr现在是两个不同的引用地址。但是让我们再来看一个潜在的问题:letarr=[1,2,{val:4}];letnewArr=arr.slice();newArr[2].val=1000;console.log(arr);//[1,2,{val:1000}]这里我们可以清楚的看到浅拷贝只能复制一层内容。但是如果复制的一级内容中有复杂的数据类型(数组/对象),那么浅拷贝就会失败,这是浅拷贝最大的局限性。不过好在深拷贝就是为了解决这个问题而诞生的。可以解决对象无限级嵌套的问题,实现一个完整的复制。我们将在下一个问题中介绍深拷贝。接下来我们总结一下浅拷贝在JS中实现的方法有哪些?2.手动实现constshallClone=(target)=>{if(typeoftarget==='object'&&target!==null){constcloneTarget=Array.isArray(target)?[]:{};for(letpropintarget){if(target.hasOwnProperty(prop)){//遍历对象本身的可枚举属性(不考虑继承属性和原型对象)cloneTarget[prop]=target[prop];}returncloneTarget;}else{returntarget;}}3.Object.assign但需要注意的是,Object.assgin()复制的是对象属性的引用,而不是对象本身。letobj={name:'sy',age:18};constobj2=Object.assign({},obj,{Newname:'sss'});console.log(obj2);//{name:"sy",age:18,Newname:"sss"}4.concat()浅拷贝数组letarr=[1,2,3];letnewArr=arr.concat();newArr[1]=100;console.log(arr);//[1,2,3]5.slice()浅拷贝开始的例子是!!6....展开运算符letarr=[1,2,3];letnewArr=[...arr];//和arr.slice()效果一样2.JS中深拷贝的手段有哪些?在实现完整版的深拷贝功能之前,我们先看看有没有API可以帮助我们完成深拷贝?1.api版本——简单版本JSON.parse(JSON.stringify());从下面的例子可以看出,简单版的深拷贝已经完成了。letarr=[10,[100,200],{x:10,y:20}];letnewArr=JSON.parse(JSON.stringify(arr));console.log(newArr[2]===arr[2];//false其实上面的api使用的是暴力方法,什么是暴力方法,这里给大家解释一下:暴力方法:将原始数据直接转成字符串,然后将字符串转成对象。(在这个时候浏览器会重新开辟所有的内存空间)来实现深拷贝。但是,我们直接使用的API往往有其自身的不足。例如,我们看下面的例子letobj={a:100,b:[10,20,30],c:{x:10},d:/^\d+$/,//d:function(){}//d:newDate()//d:BigInt('10')//d:Symbol('f')};letnewObj=JSON.parse(JSON.stringify(obj));console.log(newObj);/*{a:100,b:Array(3),c:{…},d:{…}}a:100b:(3)[10,20,30]c:{x:10}d:{}__proto__:Object}*/从输出上面的例子,我们可以看出常规属性直接变成了一个空对象,如果我们尝试将最后一个属性替换成其他类型,我们也可以find那个JSON.parse(JSON.stringify())有缺点总结一下:regular属性会变成空对象,function会直接消失,date会直接消失,stringSymbol会直接消失,BigInt('10'),会直接报错。undefined会直接消失。所以当对象中没有上述形式的属性时,可以使用JSON.parse(JSON.stringify())。但是这种方式也有一个缺点,就是循环引用问题,例如:consta={value:2},a.target=a;由于无限递归,复制a会导致系统堆栈溢出。2、实现简单版的深拷贝发现上面的缺点后,我们来手写一个版本的深拷贝。这种方法没有考虑循环引用或特殊对象的问题。函数deepClone(target){if(target===null)returnnull;if(typeoftarget!=='object')returntarget;constcloneTarget=Array.isArray(target)?[]:{};for(letpropintarget){if(target.hasOwnProperty(prop)){cloneTarget[prop]=deepClone(target[prop]);}}returncloneTarget;}现在我们以发现的几个问题为导向,依次完善深拷贝功能3.解决循环引用问题如下:letobj={value:100};obj.target=obj;deepClone(obj);//Error:RangeError:Maximumcallstacksizeexceeded这是一个循环引用。我们如何解决这个问题?创建一个Map(Map类似于一个对象,也是键值对的集合,但是“键”可以是一个对象),记录复制过的对象,如果复制过,就返回直接地。原理是每次复制类型被引用时,设置一个true作为标记,下次遍历该对象时,就知道是否被复制过。constisObject=(target)=>(typeoftarget==='object'||typeoftarget==='function')&&target!==null;functiondeepClone(target,map=newMap()){//先判断是否引用类型被复制if(map.get(target)){returntarget;}if(isObject(target)){map.set(target,true);constcloneTarget=Array.isArray(target)?[]:{};for(letpropintarget){if(target.hasOwnProperty(props)){cloneTarget[prop]=deepClone(target[props],map);}}returncloneTarget;}else{returntarget;}}现在我们可以成功了:consta={val:2};a.target=a;letnewA=deepClone(a);console.log(newA)//{val:2,target:{val:2,target:[Circular]}}好像没问题,复制完成。但是还有一个潜在的陷阱,就是地图上的key和地图形成了强引用关系,这是相当危险的。给大家解释一下对立的弱引用的概念,你就会明白:在计算机编程中,弱引用是与强引用相对的,指的是不能保证所引用的对象不被垃圾回收器回收的引用.如果一个对象仅被弱引用引用,则该对象被认为是不可访问的(或弱访问的),因此可以随时收集。--百度百科有点绕,我通俗一点解释一下,弱引用对象随时可以回收,而对于强引用,只要强引用还在,对象就不能回收。就拿上面的例子来说,map和a一直是强引用关系。在程序结束前,a占用的内存空间不会被释放,会造成严重的内存泄漏。如何解决这个问题呢?很简单,让地图的key和地图形成弱引用。ES6为我们提供了这样一个数据结构,它的名字叫WeakMap,是一种特殊的Map,里面的key是弱引用的。它的键必须是对象,而值可以是任意的。稍微修改极端类:constdeepClone=(target,map=newWeakMap())=>{//...}4.解决特殊对象问题(RegExp,Date...)如果传入对象的格式符合要求,regularor对于日期格式,返回一个新的正则或者日期对象实例$/i.test(target.constructor.name)){newconstructor(target);}}5.深度克隆完整版实现源码constisObject=(target)=>(typeoftarget==='object'||typeoftarget==='function')&&target!==null;functiondeepClone(target,map=newMap()){//先判断引用类型是否被复制if(map.get(target)){returntarget;}//检测当前对象target是否匹配正则和日期格式对象if(/^(RegExp|Date)$/i.test(target.constructor.name)){newconstructor(target);}if(isObject(target)){map.set(target,true);constcloneTarget=Array.isArray(target)?[]:{};for(letpropintarget){if(target.hasOwnProperty(props)){cloneTarget[prop]=deepClone(target[props],map);}}returncloneTarget;}else{returntarget;}}补充:对象。keys(obj)只遍历私有属性(原型上可能有公共方法,无法遍历)
