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

JavaScript复制对象和Object.assign方法无法实现深拷贝

时间:2023-04-05 13:52:41 HTML5

在JavaScript语言中,数据类型分为两类:基本数据类型和复杂数据类型。基本数据类型包括Number、Boolean、String、Null、String、Symbol(ES6新增),而复杂数据类型包括Object,而所有其他引用类型(Array、Date、RegExp、Function)、基本包装类型(Boolean、String、Number)、Math等)都是Object类型的实例对象,因此可以继承Object原型对象的一些属性和方法。对于基本数据类型,复制变量值本质上就是复制变量。修改一个变量值不会影响另一个变量。看一个简单的例子。让val=123;让copy=val;console.log(复制);//123val=456;//修改val的值对copy的值没有影响console.log(copy);//123对于复杂数据在类型上,和基本数据类型实现的不一样。对于复杂数据类型的复制,需要注意的是变量名只是一个指向这个对象的指针。当我们将一个持有对象的变量赋值给另一个变量时,实际上是在复制这个指针,两个变量都指向一个对象。因此,一个对象的修改会影响另一个对象。//obj只是一个指向对象的指针letobj={character:'peaceful'};//copy变量复制this指针,指向同一个对象letcopy=obj;console.log(copy);//{character:'peaceful'}obj.character='lovely';console.log(copy);//{character:'lovely'}有一张很形象的图描述了复制复杂数据类型的原理。同样,在复制数组时,变量名只是指向这个数组对象的指针;复制一个函数时,函数名只是指向这个函数对象的指针letarr=[1,2,3];letcopy=arr;console.log(copy);//[1,2,3]arr[0]='keith';console.log(copy);//数组对象被改变:['keith',2,3]arr=null;欢迎来到全栈开发交流计划水交流圈:582735936针对1-3年划水的前端人员,帮助突破划水瓶颈,提升思维能力console.log(copy);//['keith',2,3]即使arr=null,也不影响copy。所以此时的arr变量只是指向数组对象的指针//foo只是指向函数对象指针console.log(bar());那么,我们应该如何实现对象的深拷贝和浅拷贝呢?复制对象在JavaScript中,有两种复制对象的方法,浅复制和深复制。浅拷贝没有办法真正复制一个对象,只是保存了对象的引用;深拷贝实际上可以拷贝一个对象。浅拷贝在ES6中,Object对象新增了一个assign方法,可以实现对象的浅拷贝。这里先说说Object.assign方法的具体用法,因为后面会分析jQuery的extend方法,实现原理和Object.assign方法类似。Object.assign的第一个参数是目标对象,可以与一个或多个源对象一起使用。作为参数,将源对象的所有可枚举([[emuerable]]===true)复制到目标对象。这种复制是浅复制,复制对象时只包含对象的引用。Object.assign(target,[source1,source2,...])如果目标对象和源对象有同名的属性,后面的属性会覆盖前面的属性。如果只有一个参数,则直接返回该参数。即,Object.assign(obj)===obj如果第一个参数不是对象,而是基本数据类型(Null和Undefined除外),就会调用对应的基本封装类型。如果第一个参数是Null和Undefined,那么就会报错;如果Null和Undefined不在第一个参数中,则将跳过该参数的副本。实现对象的浅拷贝,可以使用Object.assign方法lettarget={a:123};letsource1={b:456};letsource2={c:789};欢迎加入全-栈开发与交流划水交流圈:582735936供划水1-3年前端人员帮助突破划水瓶颈,提升思维能力letobj=Object.assign(target,source1,source2);控制台日志(对象);但是对于深拷贝,无法实现Object.assign方法lettarget={a:123};letsource1={b:456};letsource2={c:789,d:{e:'lovely'}};letobj=Object.assign(target,source1,source2);source2.d.e='peaceful';console.log(obj);//{a:123,b:456,c:789,d:{e:'peaceful'}}从上面的代码可以看出,source2对象中e属性的变化,依然会影响到deepobj对象的副本。在实际的开发项目中,前后端的数据传输主要是通过JSON来实现的。JSON全称:JavaScriptObjectNotation,JavaScriptObjectNotation。JSON对象下有两个方法,一个是JSON.stringify方法,将一个JS对象转换成字符串对象;另一个是JSON.parse方法,将字符串对象转换为JS对象。这两种方法可以结合使用,实现对象的深拷贝。也就是说,当我们需要复制一个obj对象时,可以先调用JSON.stringify(obj)将其转化为字符串对象,再调用JSON.parse方法将其转化为JS对象。您可以轻松实现对象的深度复制letobj={a:123,b:{c:456,d:{e:789}}};letcopy=JSON.parse(JSON.stringify(obj));//不管你怎么修改obj对象,都不会影响copy对象obj.b.c='hello';obj.b.d.e='世界';控制台日志(复制);//{a:123,b:{c:456,d:{e:789}}}当然用这种方式实现深拷贝有个缺点,就是传递给JSON.parse的字符串method必须是合法的JSON,否则会抛出错误jQuery.extend||jQuery.fn.extend对于使用jQuery超过一定时间的朋友,jQuery.extend对象不是默认的。$.extend方法可以用来扩展jQuery的全局对象,$.fn.extend方法可以用来扩展实例对象。fn其实是原型对象的别名,所以扩展实例对象的方法其实就是给jQuery原型对象增加一些方法。$.extend方法不仅可以用来编写jQuery插件,还可以用来实现对象的深拷贝和浅拷贝。(可以用$.extend和$.fn.extend来实现深拷贝和浅拷贝,唯一的区别就是这个的方向性不一样)在源码中看到不应该在接受任何参数时,直接返回一个空对象。当只有一个参数时(该参数可以是任何数据类型(Null、Undefined、Boolean、String、Number、Object)),将返回this对象。这里有两种情况。如果使用$.extend,则返回jQuery对象;如果使用$.fn.extend,将返回jQuery原型对象。当接收两个参数且第一个参数为布尔值时,同样返回一个空对象。如果第一个参数不是布尔值,源对象将被复制到目标对象。当接收到三个以上的参数时,又分为两种情况。如果第一个参数是一个布尔值,表示深浅复制,那么目标对象将移动到第二个参数,源对象将移动到第三个参数。(目标对象、源对象和Object.assign方法相同)。如果第一个参数不是布尔值,则用法与Object.assign方法的常规副本相同。在循环源对象的过程中,任何数据类型为Null、Undefined或源对象为空对象的复制过程都将被忽略。如果源对象和目标对象具有同名的属性,则源对象的属性会覆盖目标对象的属性。如果同名属性是对象,则在deep=true等其他条件下,将属性添加到与目标对象同名的对象中。jQuery-2.1.4中jQuery.extend实现的源代码发布在jQuery.extend=jQuery下面。fn.extend=function(){varoptions,name,src,copy,copyIsArray,clone,target=arguments[0]||复制代码{},//使用||用于排除隐式转换为false类型的数据的运算符//例如''、0、undefined、null、false等。//如果target是上述值,则设置target={}i=1,length=arguments。长度,深=假;//当typeoftarget==='boolean'//将deep设置为target的值//然后将target移动到第二个参数,if(typeoftarget==="boolean"){deep=target;//使用||操作字符,不包括隐式转换为false的数据类型//例如''、0、undefined、null、false等。//如果target是上述值,则设置target={}target=arguments[i]||{};我++;}//如果target不是对象或者数组或者函数,//settarget={}//这里和Object.assign不同,//assign方法会将Boolean,String,Number方法赋值转换为对应的基本封装类型//然后返回,//并且extend方法直接将所有typeof不是object或者function的数据类型//转换为空对象if(typeoftarget!=="object"&&!jQuery.isFunction(target)){目标={};}//如果arguments.length===1or//typeofarguments[0]==='boolean',且有arguments[1],//此时目标对象会指向this//this指向哪个对象取决于使用$.fn.extend或$.extendif(i===length){target=this;//i--表示不进入for循环i--;}//循环参数类数组对象,从源对象开始for(;i