当前位置: 首页 > 科技观察

Javascript对象复制

时间:2023-03-12 11:01:15 科技观察

开始之前,先科普一下基础知识。Javascript对象只是指向内存中某个位置的指针。这些指针是可变的,也就是说,它们可以被重新分配。所以只是复制这个指针,结果是有两个指针指向内存中的同一个地址。varfoo={a:"abc"}console.log(foo.a);//abcvarbar=foo;console.log(bar.a);//abcfoo.a="yofoo";console.log(foo.a);//yofooconsole.log(bar.a);//yofoobar.a="whatupbar?";console.log(foo.a);//whatupbar?console.log(bar.a);//whatupbar?从上面的例子我们可以看出,对象foo和bar都是可以相互改变的。因此,在Javascript中复制对象时,要根据实际情况进行一些注意事项。浅拷贝如果要操作的对象的属性都是值类型,那么可以使用扩展语法或者Object.assign(...)varobj={foo:"foo",bar:"bar"};varcopy={...obj};//对象{foo:"foo",bar:"bar"}varobj={foo:"foo",bar:"bar"};varcopy=Object.assign({},obj);//Object{foo:"foo",bar:"bar"}可以看到上面两个方法可以将多个不同源对象的属性复制到一个目标对象中。varobj1={foo:"foo"};varobj2={bar:"bar"};varcopySpread={...obj1,...obj2};//对象{foo:"foo",bar:"bar"}varcopyAssign=Object.assign({},obj1,obj2);//Object{foo:"foo",bar:"bar"}上面的方法有个问题,如果对象的属性也是对象,那么它们实际上只复制了那些指针,它们与执行varbar=foo;具有相同的效果,如在第一段代码中。varfoo={a:0,b:{c:0}};varcopy={...foo};copy.a=1;copy.b.c=2;console.dir(foo);//{a:0,b:{c:2}}console.dir(copy);//{a:1,b:{c:2}}深拷贝(有限)想对一个对象进行深拷贝,一个可行的方法it就是先把对象序列化成字符串,再反序列化。varobj={a:0,b:{c:0}};varcopy=JSON.parse(JSON.stringify(obj));不幸的是,这个方法只包含对象中的可序列化值,并且没有循环引用的情况适用。无法序列化的常见事物是日期对象——尽管它显示字符串化的ISO日期格式,但JSON.parse只会将其解析为字符串,而不是日期类型。深拷贝(限制较少)对于一些更复杂的场景,我们可以使用HTML5提供的一种新算法,称为结构化克隆。然而,在撰写本文时,一些内置类型仍然不受支持,但它支持的类型比JSON.parse多得多:Date、RegExp、Map、Set、Blob、FileList、ImageData、sparse和typedArray。它还维护对克隆对象的引用,这允许它支持循环引用结构的副本,这在上述序列化中是不支持的。目前没有直接调用结构化克隆,但一些较新的浏览器功能在底层使用了这种算法。因此,深拷贝对象可能需要依赖一系列的环境来实现。ViaMessageChannels:原理是借用通信中使用的序列化算法。由于是基于事件的,所以这里的克隆也是一个异步操作。classStructuredCloner{constructor(){this.pendingClones_=newMap();this.nextKey_=0;constchannel=newMessageChannel();this.inPort_=channel.port1;this.outPort_=channel.port2;this.outPort_.onmessage=({数据:{键,值}})=>{constresolve=this.pendingClones_.get(键);解析(值);this.pendingClones_.delete(键);};this.outPort_.start();}cloneAsync(值){returnnewPromise(resolve=>{constkey=this.nextKey_++;this.pendingClones_.set(key,resolve);this.inPort_.postMessage({key,value});});}}conststructuredCloneAsync=window。structuredCloneAsync=StructuredCloner.prototype.cloneAsync.bind(newStructuredCloner);constmain=async()=>{constoriginal={date:newDate(),number:Math.random()};originaloriginal.self=original;constclone=awaitstructuredCloneAsync(原始);//不同对象:console.assert(original!==clone);console.assert(original.date!==clone.date);//cyclical:console.assert(original.self===original);console.assert(clone.self===clone);//等效值:console.assert(original.number===clone.number);console.assert(Number(original.date)===Number(clone.date));console.log("Assertionscomplete.");};main();通过historyAPI:history.pushState()和history.replaceState()都会给它们的第一个参数一个结构化的克隆!注意这个方法是同步的,因为浏览器history的运行速度不是很快。如果频繁调用该方法,浏览器会卡住};通过通知API:当创建通知实例时,构造函数对其相关数据进行结构化克隆。请注意,它会尝试向用户显示浏览器通知,但除非它收到用户允许显示通知的请求,否则它不会执行任何操作。一旦用户点击同意,通知将立即关闭。conststructuredClone=obj=>{constn=newNotification("",{data:obj,silent:true});nn.onshow=n.close.bind(n);returnn.data;};使用Node.js进行深度复制Node.js8.0.0版提供了可与结构化克隆相媲美的序列化API。然而,此API仅在本文发表时标记为实验性:constv8=require('v8');constbuf=v8.serialize({a:'foo',b:newDate()});constcloned=v8.deserialize(buf);cloned.b.getMonth();8.0.0以下版本比较稳定的方法,可以考虑使用lodash的cloneDeep函数,其思路或多或少是基于结构化克隆算法。结论Javascript中对象复制的最佳算法在很大程度上取决于它的使用环境,以及您需要复制的对象类型。虽然lodash是最安全的通用深拷贝功能,但是如果自己封装的话,或许可以获得更高效的实现方式。下面是一个简单的深拷贝,同样适用于Date对象:objinstanceofDate){copy=newDate();copy.setTime(obj.getTime());returncopy;}//HandleArrayif(objinstanceofArray){copy=[];for(vari=0,len=obj.length;i