5162

克隆JavaScript对象最有效的方法是什么?我看到了obj=eval(uneval(o));正在使用,但这是非标准的,只有Firefox支持.

我做过这样的事情obj=JSON.parse(JSON.stringify(o));但对效率提出质疑。

我还看到递归复制函数有各种缺陷。
我很惊讶没有规范的解决方案。

5
  • 566
    评估不是邪恶的。糟糕地使用eval是。如果你担心它的副作用,你就错了。你担心的副作用就是使用它的原因。顺便问一下,有人真正回答了你的问题吗? 评论 2012年3月22日14:08
  • 15
    克隆对象是一项棘手的工作,特别是对于任意集合的自定义对象。这可能就是为什么没有现成的方法来做这件事的原因。
    – b01型
    评论 2013年3月11日22:25
  • 12
    评估()通常是个坏主意,因为许多Javascript引擎的优化程序在处理通过设置的变量时必须关闭评估.只是有评估()在代码中可能会导致性能下降。 评论 2014年9月8日13:37
  • 12
    请注意JSON语言方法将丢失在JSON中没有等效项的任何Javascript类型。例如:JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))将生成{a:空,b:空,c:空,g:假} 评论 2017年5月24日13:06
  • 反应社区引入了免疫辅助因子 评论 2019年7月6日7:07

67个答案67

重置为默认值
5902

本机深度克隆

现在有一个structuredClone(值)所有主要浏览器和节点支持的函数>=17。它有旧系统的聚合物填充.

structuredClone(值)

如果需要,请先加载聚合物填料:

从“@ungap/structured-clone”导入structuredClone;

请参见这个答案了解更多详细信息,但是注意这些限制:

  • 结构化克隆算法不能复制功能对象;试图引发DataCloneError异常。
  • 克隆DOM节点同样会引发DataCloneError异常。
  • 某些对象属性不会保留:
    • RegExp对象的lastIndex属性未保留。
    • 属性描述符、setter、getter和类似元数据的功能不会重复。例如,如果一个对象用属性描述符标记为只读,那么它将在副本中读/写,因为这是默认的。
    • 原型链未漫游或复制。

更早的答案

快速克隆并丢失数据-JSON.parse/stringify

如果您不使用日期s、 功能,未定义,无穷、RegExps、Maps、Sets、Blob、FileLists、ImageData、稀疏数组、类型化数组或对象中的其他复杂类型,深度克隆对象的一个非常简单的一行程序是:

JSON.parse(JSON.stringfy(对象))

常数a={string:'字符串',编号:123,bool:错误,nul:空,date:new date(),//字符串化undef:未定义,//丢失inf:无限,//强制为“null”回复:/.*/,//丢失}控制台.log(a);console.log(日期类型);//Date对象const clone=JSON.parse(JSON.stringify(a));console.log(克隆);console.log(clone.date类型);//.toISOString()的结果

请参见科尔班的回答用于基准。

使用库进行可靠的克隆

由于克隆对象并不简单(复杂类型、循环引用、函数等),大多数主要库都提供了克隆对象的功能。轮子-如果您已经在使用库,请检查它是否具有对象克隆功能。例如,

20
  • 137
    当心!变量A={b:[{A:[1,2,3],b:[4,5,6],c:[7,8,9]}]};B=对象赋值({},A);删除B.B[0].B;它还将修改对象A! 评论 2020年9月30日12:54
  • 18
    @Gabriel Hautclocq这是因为A.b公司英国银行都指向内存中的同一对象。如果A类如果属性具有非对象值(如数字或字符串),则会正常复制。但当复制包含对象值的属性时,它是按引用而不是按值复制的。此外,请记住数组是JS中的对象。证明:typeof[]='object'&&[]instanceof数组
    – 独角兽
    评论 2020年12月15日13:21
  • 48
    @Unicornist是的,这就是为什么Object.assign没有回答这个问题:“在JavaScript中深度克隆对象的最有效方法是什么?”。因此,至少它不应该作为ES6解决方案来进行深度克隆。“ES6”的标题具有误导性,至少应该加以更改,以反映这不是一种深度克隆方法。“肤浅”这个词很容易被忽视,很多人只是在Stack Overflow中找到了最简单的解决方案,而没有阅读所有内容。依赖Object.assign进行对象克隆是危险的。所以我说。 评论 2020年12月15日13:58
  • 7
    我使用了一个名为“真正快速的深度克隆”的库:github.com/davidmarkclements/rfdc对我来说效果很好。 评论 2021年1月14日16:02
  • @里卡多当然你可以看到答案的历史,在我写下我的评论后,“(浅拷贝)”被添加到了“ES6”之后。现在更清楚的是,这是一个肤浅的复制品。 评论 2021年4月28日9:39
2496

检查此基准:http://jsben.ch/#/bWfk9网址

在我之前的测试中,我发现速度是一个主要问题

JSON.parse(JSON.stringify(obj))

深度克隆对象的最慢方式(比jQuery.扩展具有深的标志设置为真(10-20%)。

深的标志设置为(浅克隆)。这是一个很好的选择,因为它包含一些额外的类型验证逻辑,并且不会复制未定义的属性等,但这也会让您慢一点。

如果您知道要克隆的对象的结构,或者可以避免深层嵌套数组,那么可以编写一个简单的for(obj中的var i)循环来克隆对象,同时检查hasOwnProperty,这将比jQuery快得多。

最后,如果您试图在热循环中克隆已知的对象结构,只需将克隆过程内联并手动构建对象,就可以获得更高的性能。

JavaScript跟踪引擎擅长优化用于。。在里面循环和检查hasOwnProperty也会减慢速度。当速度绝对必要时,手动克隆。

var克隆对象={knownPrep:对象.knownProp,..}

小心使用JSON.parse(JSON.stringify(obj))上的方法日期对象-JSON.stringfy(新日期())以ISO格式返回日期的字符串表示形式JSON.parse() 转换回日期对象。有关更多详细信息,请参阅此答案.

此外,请注意,至少在Chrome 65中,本机克隆不是一条出路。根据JSPerf,通过创建新函数来执行本机克隆几乎800倍比使用JSON.stringify慢,JSON.stringify在所有方面都快得令人难以置信。

ES6更新

如果您使用的是Javascript ES6,请尝试使用此本机方法进行克隆或浅层复制。

赋值对象({},obj);
4
  • 14
    请注意,你的替补席上有两个错误:首先,它比较了一些浅层克隆(lodash_.clone(克隆)对象分配)进行深度克隆(JSON.parse(JSON.stringify())). 其次,它说lodash是“深层克隆”,但它做的是浅层克隆。 评论 2021年2月15日14:22
  • 请注意,在同一基准测试工具中进行测试时,对象扩散 让obj2={…obj}似乎比Object.assign()。大约快20%。
    – 拉根
    评论 2022年5月28日22:31
  • 注意,re-Object.assign({},obj})-这是一个浅层副本,而不是深层克隆。这意味着,如果属性本身就是一个对象,则只能获得一个引用。 评论 2022年12月3日15:40
  • +1000用于内联克隆。我为我的对象硬编码了一个已知属性的列表,并对其进行循环,这使我每毫秒可以复制3000个。当我去掉循环并写出对象文本中的每个属性后,每毫秒大约有200万个拷贝。绝对是疯狂的速度提升,我从来没有意识到环路会这么慢。 评论 2023年4月30日20:37
667
+50

结构化克隆

2022年更新:这个结构化克隆全局函数已在Firefox 94、Node 17和Deno 1.14中提供

HTML标准包括一种内部结构化克隆/序列化算法可以创建对象的深度克隆。它仍然局限于某些内置类型,但除了JSON支持的少数类型外,它还支持Dates、RegExp、Maps、Sets、Blobs、FileLists、ImageData、sparse Arrays、Typed Arrays,将来可能还会支持更多类型。它还保留了克隆数据中的引用,允许它支持会导致JSON错误的循环和递归结构。

Node.js中的支持:

这个结构化克隆全局函数由节点17.0提供:

const clone=structuredClone(原始);

早期版本:第8版Node.js中的模块(自节点11起)直接公开结构化序列化API,但此功能仍被标记为“实验性”,并可能在未来版本中进行更改或删除。如果您使用的是兼容版本,克隆对象非常简单:

const v8=要求(“v8”);const structuredClone=obj=>{返回v8.deserialize(v8.serialize(obj));};

浏览器中的直接支持:Firefox 94中提供

这个结构化克隆全局函数将很快由所有主要浏览器提供(之前在GitHub上的whatwg/html#793). 它看起来/将看起来像这样:

const clone=structuredClone(原始);

在发布之前,浏览器的结构化克隆实现只能间接公开。

异步解决方法:可用。😕

使用现有API创建结构化克隆的低开销方法是通过消息频道。另一个端口将发出消息事件的结构化克隆.数据不幸的是,监听这些事件必然是异步的,而同步替代方案则不太实用。

类StructuredCloner{构造函数(){this.pendingClones _=新映射();this.netKey=0;const通道=新消息通道();this.inPort _=通道端口1;this.outPort _=通道端口2;this.outPort_.onmessage=({data:{key,value}})=>{const resolve=this.pendingClones_.get(密钥);解析(值);this.pendingClones_.delete(密钥);};this.outPort_.start();}克隆异步(值){return new Promise(解决=>{const键=this.nextKey_++;this.pendingClones_.set(key,resolve);this.inPort_.postMessage({key,value});});}}const structuredCloneAsync=窗口结构化克隆异步=StructuredCloner.prototype.cloneAsync.bind(新Structured Cloner);

示例使用:

constmain=async()=>{const-original={date:new-date(),number:Math.random()};original.self=原始;const clone=等待structuredCloneAsync(原始);//它们是不同的对象:console.assert(原始!==克隆);console.assert(原始日期!==克隆日期);//它们是周期性的:console.assert(original.self===原始);console.assert(clone.self===克隆);//它们包含等效值:console.assert(original.number===clone.number);console.assert(数字(original.date)===数字(clone.date,克隆日期));console.log(“断言完成”);};main();

同步解决方案:太棒了!🤢

同步创建结构化克隆没有好的选项。这里有一些不切实际的破解。

history.pushState()历史记录.replaceState()两者都创建了第一个参数的结构化克隆,并将该值赋给历史.状态。您可以使用此选项创建任何对象的结构化克隆,如下所示:

const structuredClone=obj=>{const oldState=历史记录状态;history.replaceState(obj,null);const clonedObj=历史记录状态;history.replaceState(oldState,null);return clonedObj;};

使用示例:

'使用严格';常量main=()=>{const original={date:new date(),number:Math.random()};original.self=原始;const clone=structuredClone(原始);//它们是不同的对象:console.assert(原始!==克隆);console.assert(original.date!==clone.date);//它们是周期性的:console.assert(original.self===原始);console.assert(克隆自我===克隆);//它们包含等效值:console.assert(original.number===clone.number);console.assert(数字(original.date)===数字(clone.date,克隆日期));console.log(“断言完成”);};const structuredClone=obj=>{const oldState=历史记录状态;history.replaceState(obj,null);const clonedObj=历史记录状态;history.replaceState(oldState,null);return clonedObj;};main();

虽然是同步的,但速度可能非常慢。它会带来与操作浏览器历史记录相关的所有开销。重复调用此方法可能会导致Chrome暂时无响应。

这个通知建造师创建关联数据的结构化克隆。它还尝试向用户显示浏览器通知,但除非您请求通知权限,否则这将自动失败。如果您有其他用途的权限,我们将立即关闭我们创建的通知。

const structuredClone=obj=>{const n=新通知(“”,{data:obj,silent:true});n.onshow=n.close.bind(n);返回n.data;};

使用示例:

'使用严格';常量main=()=>{const-original={date:new-date(),number:Math.random()};original.self=原始;const clone=structuredClone(原始);//它们是不同的对象:console.assert(原始!==克隆);console.assert(原始日期!==克隆日期);//它们是周期性的:console.assert(original.self===原始);console.assert(clone.self===克隆);//它们包含等效值:console.assert(original.number===clone.number);console.assert(数字(original.date)===数字(clone.date,克隆日期));console.log(“断言完成”);};常量结构克隆=obj=>{const n=新通知(“”,{data:obj,silent:true});n.关闭();返回n.data;};main();

11
  • 59
    这真是大错特错!该API不打算以这种方式使用。 评论 2014年7月31日23:34
  • 331
    作为在Firefox中实现pushState的人,我对这次黑客攻击感到既骄傲又反感。干得好,伙计们。 评论 2014年8月14日18:37
  • 1
    pushState或Notification破解不适用于某些对象类型,如Function 评论 2019年7月3日20:06
  • 5
    2022年4月更新: 结构化克隆在FF94+、Chrome 98+、Safari 15.4+和Edge 98+中可用,因此在所有当前版本的主要浏览器中都可用!
    – 欧尔纳
    评论 2022年4月10日15:17
  • 只要HTML创作委员会根本无法设计出高质量的API,并继续大量生产出被设计破坏的API,上述滥用API的行为就会继续存在(@Jeremy勇敢地尝试展示解决方案并没有冒犯)。例如,结构化克隆算法定义了一个严格的过程(比如说,很难通过脚本进行扩展),同时将太多内容留给用户代理。例如,Firefox无法克隆错误例如,对象--但MDN自豪地声明它支持结构化克隆和朋友,尽管这是一种解释方式。 评论 2022年5月4日12:57
570

假设对象中只有属性而没有任何函数,则可以使用:

var newObject=JSON.parse(JSON.stringify(oldObject));
5
  • 功能日期 评论 2020年10月24日12:54
  • 13
    具有循环属性的对象失败 评论 2021年9月7日6:27
  • 2
    或Sets或其他非JSON可序列化属性 评论 2022年2月8日4:20
  • 2
    我想这是克隆数据对象的最好方法。特别是当您处理从API和状态管理中获取的数据时。我认为,如果您需要克隆一个使用本机Javascript构造(函数、日期、NaN…)创建的对象,则存在问题,或者很可能不需要克隆它。 评论 2022年5月17日9:40
  • 1
    对于以下类型,这是低效的NaN,无限,未定义等。JSON.stringify将它们转换为null。参考文献:JSON.parse(JSON.stringify({a:null,b:undefined}))等于{a:空}. 评论 2022年8月27日12:32
381

如果没有内置的,您可以尝试:

功能克隆(obj){if(obj===null||typeof(obj)!=='obj中的对象“||”isActiveClone)返回对象;if(obj实例日期)var temp=新对象构造函数()//或新日期(obj);其他的var temp=对象构造函数();for(obj中的var键){if(Object.prototype.hasOwnProperty.call(obj,key)){obj['isActiveClone']=空;temp[key]=克隆(obj[key]);删除对象['isActiveClone'];}}返回温度;}
  • 6
    你能解释一下吗isActiveClone分开一点? 评论 2022年8月6日20:19
  • 它似乎跟踪循环引用。例如。:常数a={};a['selfref']=a;a['text']='something';警报(a.selfref.text);如果你试图克隆以上内容不带的对象isActiveClone部分,您将以无限递归结束(因为递归克隆()财产征用自我参照).
    – 佩蒂29
    评论 2023年3月13日11:29
  • 这里最好的答案之一阵列循环引用以及具有可接受速度(性能)的干净代码 评论 2023年4月4日18:45
169

在一行代码中克隆(而不是深度克隆)对象的有效方法

对象分配该方法是ECMAScript 2015(ES6)标准的一部分,可以满足您的需要。

var clone=Object.assign({},obj);

assign()方法用于将所有可枚举自身属性的值从一个或多个源对象复制到目标对象。

阅读更多。。。

这个聚乙烯填料要支持较旧的浏览器:

if(!Object.assign){Object.defineProperty(对象,“assign”{可枚举:false,可配置:true,可写:true,值:函数(目标){'使用严格';if(目标===未定义||目标===null){throw new TypeError(“无法将第一个参数转换为对象”);}var to=对象(目标);for(var i=1;i<参数长度;i++){var nextSource=参数[i];if(nextSource===未定义||nextSource===空){继续;}nextSource=对象(nextSource);var keysArray=Object.keys(nextSource);for(var nextIndex=0,len=keysArray.length;nextIndex<len;next Index++){var nextKey=keysArray[nextIndex];var desc=Object.getOwnPropertyDescriptor(nextSource,nextKey);if(desc!==未定义&&desc.enumerable){to[nextKey]=nextSource[nexdKey];}}}返回;}});}
5
  • 105
    这并不是递归复制,因此并不能真正解决克隆对象的问题。
    – 米怀特
    评论 2016年3月8日19:56
  • 7
    尽管我测试了一些方法,但这种方法还是有效的,_.extend({},(obj))是最快的:例如,比JSON.parse快20倍,比Object.assign快60%。它可以很好地复制所有子对象。
    – 尼科
    评论 2016年5月9日19:57
  • 18
    @mwhite克隆和深克隆之间有区别。这个答案实际上是克隆的,但不是深度克隆的。 评论 2016年6月8日12:08
  • 4
    问题是关于递归副本。Object.assign以及给定的自定义赋值不递归复制 评论 2021年3月4日1:52
  • 这个答案不是一个深层次的克隆,也不是问题的意义所在。 评论 2022年11月8日15:07
138

按性能进行深度复制:

根据基准从最好到最差进行排名
https://www.measurethat.net/Bechmarks/Show/17502/0/deep-copy-comparison网站

  • 扩散算子...(基元数组-仅限)
  • 切片()(基元数组-仅限)
  • 拼接(0)(基元数组-仅限)
  • concat()(基元数组-仅限)
  • JSON.parse(JSON.stringify())(仅限基本数组和文字数组)
  • 自定义函数,如下所示(任意数组)
  • 洛达什的_.cloneDeep()(任意数组)
  • jQuery的$.extend()(任意数组)
  • 下划线_.clone()(仅限基本数组和文字数组)

哪里:

  • 基元=字符串、数字和布尔值
  • literals=对象文字{},数组文字[]
  • any=原语、文字和原型

深度复制图元数组:

设arr1a=[1,'a',true];

要深度复制仅包含基元(即数字、字符串和布尔值)的数组,请重新分配,切片(),concat()和下芯克隆()可以使用。

传播速度最快的地方:

设arr1b=[…arr1a];

在哪里切片()性能优于拼接(0)concat()

设arr1d=arr1a.slice();设arr1c=arr1a.拼接(0);设arr1e=arr1a.concat();

深度复制基本体和对象文字数组:

设arr2a=[1,'a',true,{},[]];让arr2b=JSON.parse(JSON.stringify(arr2a));

深度复制一组基本体、对象文本和原型:

设arr3a=[1,'a',true,{},[],new Object()];

编写自定义函数(性能比jQuery更快$.extend())以下为:

函数副本(aObject){//防止未定义的对象//if(!aObject)返回aObject;让bObject=Array.isArray(aObject)?[] : {};出租价值;for(aObject中的常量键){//防止自我引用父对象//如果(Object.is(aObject[key],aObject))继续;value=对象[key];bObject[key]=(值类型===“对象”)?copy(value):值;}return bObject;}设arr3b=副本(arr3a);

或者使用第三方实用程序功能:

设arr3c=$.extend(true,[],arr3a);//jQuery(jQuery)设arr3d=_.cloneDeep(arr3a);//洛达什
6
  • 2
    对于for-in循环,您应该使用拥有自己的财产排除继承的属性。我使用(可能更快)plain for loop over对象.键. 评论 2021年6月29日6:19
  • 在深度复制中,您不想同时复制继承的属性吗?另外,请注意调用拥有自己的财产方法,为每个键创建性能命中(将函数调用推上堆栈和推下堆栈,并执行方法代码)。 评论 2021年6月30日17:47
  • 1
    其中大多数都不是深度克隆,因此将它们相互比较是没有意义的。 评论 2022年11月8日15:07
  • 1
    在我的测试中切片()拼接(0),尽管它们都是肤浅的复制品 评论 2023年4月4日17:12
  • @美国。Serpooshan-同意,答案已根据以下基准进行了更新(measurehat.net/Bechmarks/Show/17502/0/deep-copy-comparison公司) 评论 2023年4月4日23:01
124

这就是我使用的:

函数cloneObject(obj){var克隆={};for(obj中的var i){if(typeof(obj[i])==“object”&&对象[i]!=null)clone[i]=克隆对象(obj[i]);其他的克隆[i]=对象[i];}返回克隆;}
5
  • 尝试:vara={b:1,c:3,d:{a:10,g:20,h:{today:newDate()}}};不为我工作。但是对象.assign({},a)做。 评论 2021年4月8日19:58
  • 更糟的是,试试设o={};o.o=o;克隆对象(o); 评论 2021年4月27日17:42
  • 注意:这不适用于日期 评论 2022年7月6日2:26
  • 这对数组不起作用,因为它会将它们转换为对象:{a:[“foo”,“bar”}}将成为{a{“0”:“foo”,“1”:“bar”}}. 评论 2022年11月8日15:08
  • 我已经为复杂对象扩展了此函数,但现在它不支持Datestackblitz.com/edit/typescript-vudfgn?文件=索引.ts 评论 2023年2月3日16:11
115

代码:

//使用成员从“到”扩展“from”对象。如果“to”为空,则返回“from”的深度克隆函数扩展(从,到){if(from==null||typeoffrom!=“object”)返回自;if(from.constructor!=对象&&from.constructor!=数组)从返回;if(from.constructor==日期|| from.confructor==RegExp|| from constructor==函数||from.constructor==字符串||from.confructor==Number ||from constructor==布尔值)从.constructor(from)返回new;to=to | | new from.constructor();for(from中的var名称){to[name]=typeof to[name]==“undefined”?扩展(从[name],null):到[name];}返回;}

测试:

变量对象={date:new date(),func:函数(q){return 1+q;},编号:123,text:“asdasd”,数组:[1,“asd”],regex:新RegExp(/aaa/i),子bj:{数量:234,text:“asdsaD”}}var克隆=扩展(obj);
1
103

用JavaScript深度复制对象(我认为最好也是最简单的)

1.使用JSON.parse(JSON.stringify(object));

变量对象={a: 1中,b: {c: 2个}}var newObj=JSON.parse(JSON.stringify(obj));对象b.c=20;控制台.log(obj);//{a:1,b:{c:20}}console.log(newObj);//{a:1,b:{c:2}}

2.使用创建的方法

函数cloneObject(obj){var克隆={};for(obj中的var i){if(obj[i]!=空&&typeof(obj[2])==“对象”)clone[i]=克隆对象(obj[i]);其他的克隆[i]=对象[i];}返回克隆;}变量对象={a: 1中,b: {c: 2个}}var newObj=克隆对象(obj);对象b.c=20;console.log(obj);//{a:1,b:{c:20}}console.log(newObj);//{a:1,b:{c:2}}

3.使用Lo-Dash的_.cloneDeep链接洛达什

变量对象={a: 1中,b: {c: 2个}}var newObj=_.cloneDeep(obj);对象b.c=20;控制台.log(obj);//{a:1,b:{c:20}}console.log(newObj);//{a:1,b:{c:2}}

4.使用Object.assign()方法

变量对象={a: 1中,b: 2个}var newObj=_.clone(obj);对象b=20;控制台.log(obj);//{a:1,b:20}console.log(newObj);//{a:1,b:2}

但错误的时间

变量对象={a: 1中,b: {c: 2个}}var newObj=对象分配({},obj);对象b.c=20;控制台.log(obj);//{a:1,b:{c:20}}console.log(newObj);//{a:1,b:{c:20}-->错误//注意:无法复制原型链上的属性和非枚举属性。

5.使用Underscore.js _.clone链接下划线.js

变量对象={a: 1中,b: 2个}var newObj=_.clone(obj);对象b=20;控制台.log(obj);//{a:1,b:20}console.log(newObj);//{a:1,b:2}

但错误的时间

var obj={a: 1中,b: {c: 2个}}var newObj=_.cloneDeep(obj);对象b.c=20;控制台.log(obj);//{a:1,b:{c:20}}console.log(newObj);//{a:1,b:{c:20}-->错误//(创建所提供普通对象的浅复制克隆。任何嵌套对象或数组都将通过引用进行复制,而不是复制。)

JSBEN公司。CH绩效标杆游戏场1~3http://jsben.ch/KVQLd 性能在JavaScript中深度复制对象

  • 1
    嘿,你上一个例子错了。在我看来,对于错误的示例,必须使用_clone而不是_cloneDeep。 评论 2019年12月10日7:20
  • 1
    这个创建的方法(2.)不适用于数组,是吗? 评论 2019年12月18日14:06
  • 1
    方法#2易受原型污染的影响,类似于洛达什的情况默认深度。如果(i===‘__proto__'),如果(i===‘constructor’&对象类型[i]===’function’). 评论 2021年3月7日8:46
83

在JS中,克隆对象一直是一个问题,但这都是关于ES6之前的事,我在下面列出了用JavaScript复制对象的不同方法,假设您有下面的对象,并希望有一个深入的副本:

变量obj={a:1,b:2,c:3,d:4};

有几种方法可以在不更改原点的情况下复制此对象:

  1. ES5+,使用简单的功能为您复制:

    函数deepCopyObj(obj){如果(null==obj||“object”!=typeofobj)返回obj;if(obj实例日期){var copy=新日期();copy.setTime(obj.getTime());返回副本;}if(obj实例数组){var副本=[];对于(var i=0,len=obj.length;i<len;i++){copy[i]=deepCopyObj(obj[i]);}返回副本;}if(obj对象实例){var-copy={};for(obj中的var属性){如果(obj.hasOwnProperty(attr))copy[attr]=deepCopyObj(obj[attr]);}返回副本;}throw new Error(“无法复制对象this object.”);}
  2. ES5+,使用JSON.parse格式JSON.stringify格式.

    var deepCopyObj=JSON.parse(JSON.stringify(obj));
  3. 角度:

    var deepCopyObj=角度复制(obj);
  4. jQuery(查询):

    var deepCopyObj=jQuery.extend(true,{},obj);
  5. Underscore.js和Lodash:

    var deepCopyObj=_.cloneDeep(obj)//Underscore.js的最新版本进行了浅层复制

希望这些帮助…

0
72
var克隆=函数(){var newObj=(此数组实例)?[] : {};for(此处为var i){if(此[i]&&此[i]的类型==“对象”){newObj[i]=此[i].clone();}其他的{newObj[i]=此[i];}}return newObj;}; Object.defineProperty(Object.protype,“clone”,{value:clone,enumerable:false});
0
67

有一个库(称为“克隆”),这做得很好。它提供了我所知的最完整的任意对象递归克隆/复制。它还支持循环引用,其他答案还没有涵盖。

你可以在npm上找到它也是。它可以用于浏览器以及Node.js。

下面是如何使用它的示例:

使用安装

npm安装克隆

或者用包装恩德.

ender构建克隆[…]

您还可以手动下载源代码。

然后可以在源代码中使用它。

var clone=require(“克隆”);vara={foo:{bar:'baz'}};//a的初始值var b=克隆(a);//克隆a->ba.foo.bar='foo';//更改a控制台.log(a);//{foo:{bar:“foo”}}控制台.log(b);//{foo:{bar:'baz'}}

(免责声明:我是图书馆的作者。)

0
57

我知道这是一个老帖子,但我认为这可能对下一个蹒跚而行的人有所帮助。

只要不将对象指定给任何对象,它就不会在内存中保持引用。因此,要制作一个要与其他对象共享的对象,必须创建这样的工厂:

var a=函数(){返回{父亲:‘撒迦利亚’};},b=a(),c=a();c.father=“johndoe”;警惕(b.父亲);
1
  • 这不是对现有对象的深度克隆,这只是在创建一个新对象。 评论 2022年11月8日15:09
50

如果你正在使用它下划线.js图书馆有一个克隆方法。

var newObject=_.clone(旧对象);
1
  • 2
    这是一个浅拷贝,而不是OP所寻找的深拷贝。 评论 2021年9月30日9:15
47

下面是ConroyP上述答案的一个版本,即使构造函数具有所需的参数,该版本仍然有效:

//如果Object.create尚未定义,我们只需进行简单的填充,//没有第二个论点,因为这就是我们这里所需要的var object_create=对象创建;if(typeof object_create!==“函数”){object_create=函数(o){函数F(){}F.原型=o;返回新的F();};}函数deepCopy(obj){if(obj==null||typeof(obj)!=='对象'){返回对象;}//确保返回的对象与原始对象具有相同的原型var ret=对象创建(obj.constructor.prototype);for(obj中的var键){ret[key]=深度复制(obj[key]);}返回ret;}

此功能在我的单纯形库。

编辑:

下面是一个更健壮的版本(由于Justin McCandless,现在也支持循环引用):

/***深度复制对象(复制其所有对象属性、子属性等)*的改进版本http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone*如果构造函数具有必需的参数,则不会中断* *它还借用了一些代码http://stackoverflow.com/a/11621004/560114*/ 函数deepCopy(src,/*内部*/_visited,_copiesVisited){if(src===null||typeof(src)!=='对象'){返回src;}//遵循本机/自定义克隆方法if(src.clone的类型==“函数”){return src.clone(true);}//特殊情况://日期if(src-instanceof Date){return new Date(src.getTime());}//注册Expif(RegExp的src实例){return new RegExp(src);}//DOM元素if(src.nodeType&&typeof src.cloneNode==“函数”){return src.cloneNode(true);}//如果需要,初始化访问的对象阵列。//这用于检测循环引用。if(已访问===未定义){_已访问=[];_copiesVisited=[];}//检查此对象是否已被访问var i,len=访问长度;对于(i=0;i<len;i++){//如果是,请获取我们已经制作的副本if(src===访问[i]){return _copiesVisited[i];}}//阵列if(Object.prototype.toString.call(src)==“[Object Array]”){//[].slice()本身将进行软克隆var ret=src.slice();//将其添加到已访问数组_visited.push(src);_copiesVisited.push(ret);var i=重试长度;同时(i--){ret[i]=深度复制(ret[i],_visited,_copiesVisited);}返回ret;}//如果我们到达这里,我们有一个常规对象//确保返回的对象与原始对象具有相同的原型var proto=(Object.getPrototypeOf?Object.getProtoypeOv(src):src__原型);if(!原型){proto=src.constructor.protype//这一行可能只有非常旧的浏览器才能访问}var dest=对象创建(原型);//将此对象添加到访问过的数组_visited.push(src);_copiesVisited.push(dest);for(src中的var键){//注意:这不会保留ES5属性属性,如“可写”、“可枚举”等。//有关如何对此进行修改的示例,请参见singleMixin()函数dest[key]=deepCopy(src[key],_visited,_copiesVisited);}返回目的地;}//如果Object.create还没有定义,我们只需要做简单的填充,//没有第二个论点,因为这就是我们这里所需要的var object_create=对象创建;if(typeof object_create!==“函数”){object_create=函数(o){函数F(){}F.原型=o;返回新的F();};}
33

下面创建了同一对象的两个实例。我找到了它,目前正在使用。它简单易用。

var objToCreate=JSON.parse(JSON.stringify(cloneThis));
0
31

克罗福德建议(我更喜欢)使用此函数:

函数对象(o){函数F(){}F.原型=o;返回新的F();}var newObject=对象(oldObject);

它很简洁,可以按预期工作,而且您不需要库。


编辑:

这是用于对象.创建,所以您也可以使用它。

var newObject=对象.create(oldObject);

注:如果您使用其中的一些,您可能会遇到使用拥有自己的财产.因为,创造创建继承的新空对象旧对象。但对于克隆对象来说,它仍然是有用且实用的。

例如,如果oldObject.a=5;

newObject.a;//是5

但是:

oldObject.hasOwnProperty(a);//是真的newObject.hasOwnProperty(a);//是false
0
25
功能克隆(obj){var克隆={};克隆.原型=obj.原型;for(obj中的属性)clone[属性]=obj[属性];返回克隆;}
0
23

Lodash有一个漂亮的_.cloneDeep(值)方法:

var对象=[{'a':1},{'b':2}];var depth=_.cloneDeep(对象);console.log(深度[0]===对象[0]);//=>假
0
22

浅拷贝单层(ECMAScript第5版)以下为:

varorigin={foo:{}};var copy=Object.keys(origin).reduce(函数(c,k){c[k]=origin[k];返回c;},{});console.log(原始、副本);console.log(origin==副本);//console.log(origin.foo==副本.foo);//真的

和浅拷贝单线(ECMAScript第6版, 2015):

varorigin={foo:{}};var-copy=Object.assign({},原点);console.log(源,副本);console.log(origin==副本);//console.log(origin.foo==副本.foo);//真的
1
  • 这是一个浅层拷贝和一个深拷贝就像所问的问题。这不适用于嵌套对象。 评论 2022年11月8日15:10
21

对于类数组对象,似乎还没有理想的深度克隆操作符。如下面的代码所示,John Resig的jQuery cloner将具有非数值属性的数组转换为非数组的对象,RegDwight的JSON cloner删除了非数值属性。以下测试在多个浏览器上演示了这些要点:

函数jQueryClone(obj){return jQuery.extend(true,{},obj)}函数JSONClone(obj){return JSON.parse(JSON.stringify(obj))}var arrayLikeObj=[[1,“a”,“b”],[2,“b“,”a“]];arrayLikeObj.names=[“m”,“n”,“o”];var JSONCopy=JSONClone(arrayLikeObj);var jQueryCopy=jQueryClone(arrayLikeObj);alert(“arrayLikeObj是数组实例吗?”+(array的arrayLikeObj实例)+“\njQueryClone是数组实例吗?”+(jQueryCopy instanceof array)+“arrayLikeObj名称是什么?”+arrayLike Obj.names+“\n JSONClone名称是什么?”+JSONCopy.names)
0
19

只是因为我没看见AngularJS公司提到并认为人们可能想知道。。。

角度复制还提供了一种深度复制对象和数组的方法。

2
  • 或者可以使用与jQiery extend相同的方式:角度延伸({},obj); 评论 2016年9月21日9:07
  • 2
    @加利瓦尼:应该注意jQuery.扩展角度.延伸都是肤浅的复制品。角度复制是一个深度复制。 评论 2016年10月15日18:41
18

根据您的目标是不是克隆一个“普通的旧JavaScript对象”,我有两个很好的答案。

我们还假设您的意图是创建一个完整的克隆,并且没有对源对象的原型引用。如果您对完整的克隆不感兴趣,那么可以使用其他一些答案中提供的许多Object.clone()例程(Crockford的模式)。

对于普通的旧JavaScript对象,在现代运行时中克隆对象的一种行之有效的好方法非常简单:

var clone=JSON.parse(JSON.stringify(obj));

请注意,源对象必须是纯JSON对象。也就是说,它的所有嵌套属性都必须是标量(如布尔值、字符串、数组、对象等)。任何函数或特殊对象(如RegExp或Date)都不会被克隆。

它有效率吗?见鬼,是的。我们尝试了各种克隆方法,效果最好。我相信一些忍者会想出一个更快的方法。但我怀疑我们谈论的是边际收益。

这种方法简单易行。把它包装成一个方便的函数,如果你真的需要挤出一些收益,请稍后再做。

现在,对于非显式JavaScript对象,没有一个真正简单的答案。事实上,这是不可能的,因为JavaScript函数和内部对象状态的动态特性。深度克隆包含函数的JSON结构需要重新创建这些函数及其内部上下文。JavaScript根本没有一种标准化的方法来实现这一点。

要做到这一点,正确的方法是通过一个方便的方法,您可以在代码中声明并重用该方法。方便的方法可以赋予您对自己对象的一些理解,这样您可以确保在新对象中正确地重新创建图形。

我们是自己编写的,但这里介绍了我见过的最佳通用方法:

http://davidwalsh.name/javascript-clone

这是正确的想法。作者(David Walsh)评论了广义函数的克隆。根据您的用例,您可能会选择这样做。

其主要思想是,您需要在每种类型的基础上专门处理函数(或者说原型类)的实例化。这里,他为RegExp和Date提供了几个示例。

这段代码不仅简短,而且可读性很强。它很容易扩展。

这有效吗?见鬼,是的。鉴于目标是生成真正的深度复制克隆,那么您必须遍历源对象图的成员。使用这种方法,您可以精确地调整要处理的子成员以及如何手动处理自定义类型。

好了,就这样。两种方法。在我看来,两者都很有效。

18

我很晚才回答这个问题,但我有另一种克隆对象的方法:

函数cloneObject(obj){if(obj===null||typeof(obj)!=='对象')返回对象;var temp=对象构造函数();//改变for(obj中的var键){if(Object.prototype.hasOwnProperty.call(obj,key)){obj['isActiveClone']=空;temp[key]=克隆对象(obj[key]);删除对象['isActiveClone'];}}返回温度;}var b=克隆对象({“a”:1,“b”:2});//打电话

这比以下方法更好更快:

变量a={“a”:1,“b”:2};var b=JSON.parse(JSON.stringify(a));

变量a={“a”:1,“b”:2};//深度复制var newObject=jQuery.extend(true,{},a);

我已经对代码进行了基准测试,您可以测试结果在这里:

并分享结果:在此处输入图像描述参考文献:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty网站

4
  • 这很有趣,但当我运行您的测试时,它实际上让我感到方法1是最慢的方法 评论 2018年4月26日8:30
  • 和我一样,1号街区是最低的! 评论 2018年12月5日1:08
  • 唯一对我有效的解决方案!必须深度克隆包含其他具有函数属性的对象的对象。很 完美。
    – 凤凰
    评论 2021年3月12日17:22
  • 你为什么设置obj['isActiveClone']=空然后删除它?你为什么不打电话对象hasOwnProperty(键)? 评论 2021年4月16日7:50
18

只有当您可以使用ECMA脚本6转发器.

特征:

  • 复制时不会触发getter/setter。
  • 保留getter/setter。
  • 保留原型信息。
  • 适用于两者对象-边功能性的 面向对象写作风格。

代码:

功能克隆(目标、源){for(让密钥进入源){//使用getOwnPropertyDescriptor而不是source[key]来防止触发setter/getter。let描述符=Object.getOwnPropertyDescriptor(源,键);if(字符串的描述符.value实例){target[key]=新字符串(descriptor.value);}else if(数组的描述符.value实例){target[key]=克隆([],descriptor.value);}else if(描述符.对象的值实例){let prototype=Reflect.getPrototypeOf(descriptor.value);让cloneObject=克隆({},descriptor.value);Reflect.setPrototypeOf(cloneObject,prototype);target[key]=克隆对象;}其他{Object.defineProperty(目标、键、描述符);}}let prototype=Reflect.getPrototypeOf(源);Reflect.setPrototypeOf(目标,原型);返回目标;}
2
  • 1
    数据类型有问题,如日期 评论 2021年10月15日9:31
  • 这将创建对同一对象实例的引用(deep-copying it)如果与具有无效的原型,自Object.create(null)instanceof对象为false。
    – 樱桃DT
    评论 2021年10月31日18:05
16

这通常不是最有效的解决方案,但它满足了我的需要。下面的简单测试用例。。。

功能克隆(obj,克隆){//制作“obj”的深度副本。处理循环结构//在“clones”参数中跟踪克隆的obj。功能//包括但不克隆。函数成员被克隆。变量new_obj,已经克隆,t=对象类型,i=0,我,一对;clones=克隆||[];if(obj===空){返回对象;}if(t===“对象”||t==“函数”){//看看我们是否已经克隆了obj对于(i=0,l=克隆长度;i<l;i++){pair=克隆[i];if(对[0]===obj){already_clone=对[1];断裂;}}if(已克隆){return already_clone;}其他{if(t===“对象”){//创建新对象new_obj=新对象构造函数();}else{//按原样使用函数new_obj=对象;}clones.push([obj,new_obj]);//跟踪我们克隆的对象for(obj中的键){//克隆对象成员if(obj.hasOwnProperty(键)){new_obj[key]=克隆(obj[key],克隆);}}}}return new_obj | obj;}

循环数组测试。。。

a=[]a.推动(“b”、“c”、a)aa=克隆(a)aa===a//=>假aa[2]===a//=>错误aa[2]===a[2]//=>假aa[2]===aa//=>真

功能测试。。。

f=新功能f.a=aff=克隆(f)ff==f//=>真ff.a===a//=>假
16

对于想要使用JSON.parse(JSON.stringify(obj))版本,但在不丢失Date对象的情况下,可以使用第二个参数解析方法将字符串转换回Date:

功能克隆(obj){var regExp=/^\d{4}-\d日{2}-\d日{2} T型\d{2}:\d{2{:\d{2}\。\d日{3} Z轴$/;return JSON.parse(JSON.stringify(obj),函数(k,v){if(类型v===“字符串”&&regExp.test(v))return新日期(v)返回v;})}//用法:var原始={a: [1,null,未定义,0,{a:null},new Date()],b:{c(){返回0}}}var cloned=克隆(原始)console.log(克隆)

1
14

我不同意投票最多的答案在这里.A型递归深度克隆速度快得多JSON.parse(JSON.stringify(obj))提到的方法。

下面是供快速参考的函数:

函数cloneDeep(o){让newO让我来吧if(typeof o!==“object”)返回o如果(!o)返回oif(Object.protype.toString.apply(o)==='[Object Array]'){新O=[]对于(i=0;i<o.length;i+=1){newO[i]=克隆深度(o[i])}返回newO}新O={}for(i in o){如果(o.hasOwnProperty(i)){newO[i]=克隆深度(o[i])}}返回newO}
  • 2
    我喜欢这种方法,但它不能正确处理日期;考虑添加以下内容if(o instanceof Date)返回新的Date(o.valueOf());检查null后`
    – 路易斯
    评论 2017年8月21日22:53
  • 循环引用崩溃。
    – 哈利
    评论 2018年3月18日5:19
  • 在最新稳定的Firefox中,这比Jsben.ch链接上的其他策略要长一个数量级或更多。它在错误的方向上击败了其他人。 评论 2019年1月14日18:38
14
//obj目标对象,vals源对象var setVals=函数(obj,vals){if(对象和值){for(vals中的var x){if(vals.hasOwnProperty(x)){if(obj[x]&&typeof vals[x]===“对象”){obj[x]=设置值(obj[x],vals[x]);}其他{obj[x]=值[x];}}}}返回对象;};

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.