3760

我有一个物体x个。我想将其复制为对象,因此更改为不要修改x个。我意识到复制从内置JavaScript对象派生的对象将导致额外的、不需要的属性。这不是问题,因为我正在复制我自己的一个文本构造对象。

如何正确克隆JavaScript对象?

26
  • 35
    请参阅此问题:stackoverflow.com/questions/122102/… 评论 2011年6月21日10:13
  • 291
    对于JSON,我使用mObj=JSON.parse(JSON.stringify(jsonObject)); 评论 2013年2月2日10:09
  • 80
    我真的不明白为什么没有人建议对象.create(o),它能满足作者的要求吗?
    – 冷冻
    评论 2014年8月8日15:23
  • 60
    varx={deep:{key:1}};var y=对象创建(x);x.deep.key=2;完成此操作后,y.dep.key键也将是2,因此Object.create不能用于克隆。。。 评论 2015年7月4日15:04
  • 23
    @r3wt不起作用……请在对溶液进行基本测试后发布。。
    – 用户3275211
    评论 2016年2月16日18:54

82个答案82

重置为默认值
1994

2022年更新

有一个新的JS标准叫做结构化克隆。它适用于许多浏览器(请参阅我能用吗).

const clone=structuredClone(对象);

老答案

对JavaScript中的任何对象执行此操作都不简单。您将遇到错误地从对象的原型中提取属性的问题,这些属性应该留在原型中,而不是复制到新实例中。例如,如果您要添加克隆方法到对象.原型如一些答案所述,您需要显式跳过该属性。但如果有其他附加方法添加到对象.原型或其他你不知道的中间原型?在这种情况下,您将复制不应该复制的属性,因此需要使用拥有自己的财产方法。

除了非枚举属性之外,当您尝试复制具有隐藏属性的对象时,还会遇到更困难的问题。例如,原型是函数的隐藏属性。此外,对象的原型是用属性引用的__原型__,它也是隐藏的,不会被for/in循环在源对象的属性上迭代复制。我想__原型__可能特定于Firefox的JavaScript解释器,在其他浏览器中可能有所不同,但您可以了解情况。并不是所有的东西都是可枚举的。如果知道隐藏属性的名称,就可以复制它,但我不知道如何自动发现它。

寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果源对象的原型是对象,然后使用{}将起作用,但如果源代码的原型是对象,那么您将丢失使用拥有自己的财产过滤器,或原型中的,但一开始不可枚举的。一种解决方案可能是调用源对象的建造师属性获取初始复制对象,然后复制属性,但仍然无法获得非枚举属性。例如日期对象将其数据存储为隐藏成员:

功能克隆(obj){如果(null==obj||“object”!=typeofobj)返回obj;var copy=对象构造函数();for(obj中的var属性){如果(obj.hasOwnProperty(attr))copy[attr]=obj[attr];}返回副本;}var d1=新日期();/*5秒后执行功能*/setTimeout(函数(){var d2=克隆(d1);警报(“d1=”+d1.toString()+“\nd2=”+d2.toString());}, 5000);

的日期字符串第1天将比第2天.一种制作方法日期通过调用设置时间方法,但这是特定于日期类。我不认为这个问题有一个防弹的通用解决方案,尽管我很乐意出错!

当我必须实现一般的深度复制时,我最终做出了妥协,认为我只需要复制一个普通的对象,阵列,日期,字符串,编号,或布尔值。最后3种类型是不可变的,所以我可以执行浅层复制,而不用担心它会更改。我进一步假设对象阵列也将是该列表中的6种简单类型之一。这可以通过以下代码实现:

功能克隆(obj){var复制;//处理3个简单类型,null或未定义如果(null==obj||“object”!=typeofobj)返回obj;//处理日期if(obj实例日期){copy=新日期();copy.setTime(obj.getTime());返回副本;}//句柄数组if(obj实例数组){copy=[];对于(var i=0,len=obj.length;i<len;i++){copy[i]=克隆(obj[i]);}返回副本;}//处理对象if(obj对象实例){copy={};for(obj中的var属性){如果(obj.hasOwnProperty(attr))copy[attr]=克隆(obj[attr]);}返回副本;}throw new Error(“无法复制obj!不支持其类型。”);}

只要对象和数组中的数据形成树状结构,上述函数就足以处理我提到的6种简单类型。也就是说,对象中对同一数据的引用不超过一个。例如:

//这将是可克隆的:var树={“left”:{“left“:null,”right“:null,”data“:3},“right”:空,“数据”:8};//这有点管用,但你会得到两份//内部节点,而不是对同一副本的2个引用var directedAcylicGraph={“left”:{“left”:null,“right”:null,“data”:3},“数据”:8};directedAcyclicGraph[“right”]=directedAcyclicGraph[“left”];//克隆它会由于无限递归而导致堆栈溢出:var cyclicGraph={“left”:{“left“:null,”right“:null,”data“:3},“数据”:8};cyclicGraph[“right”]=cyclicGraph;

它将无法处理任何JavaScript对象,但对于许多目的来说,它可能已经足够了,只要您不假定它只适用于您向它抛出的任何东西。

9
  • 这缺少符号键和符号值。如今,使用Object.getOwn属性描述符更好。 评论 2021年7月10日15:33
  • 结构化克隆全球只有75%兼容 评论 2022年5月17日21:27
  • 2
    更新@JoshuaDavid,目前82.57%的浏览器支持该功能。 评论 2022年6月15日14:23
  • 2
    如果我想怎么办克隆这个对象也希望追加有什么事吗? 评论 2022年9月6日14:38
  • 1
    在移动设备上使用此功能时遇到问题…-iOS 11,12,13,14,不适用于Safari和Google Chrome。(适用于iOS 15、16)-安卓12、11、10、9,适用于三星互联网浏览器(chrome适用)。
    – jfx忍者
    评论 2022年9月21日14:49
1165

如果您不使用日期s、 函数、未定义函数、regExp或对象中的Infinity,一个非常简单的一行程序是JSON.parse(JSON.stringify(对象)):

常数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()的结果

这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。

另请参见关于浏览器的结构化克隆算法在向工作人员发送消息和从工作人员发送邮件时使用。它还包含深度克隆功能。

4
  • 1
    有时最好的答案是最简单的。天才。
    – 结露
    评论 2021年11月23日16:30
  • 这很有用,但在比较包含其他对象的对象时,当两个完全相等的对象不相等时,我会遇到意外的行为。使用了JSON.stringify(x)==JSON.strongify。 评论 2022年4月28日13:36
  • @阿古斯丁。您无法在JS中比较复杂的数据类型。a={};b={};a==b。但之后a=b它变成了真的,因为它不仅是相同的,而且是同一个对象。 评论 2022年4月28日18:47
  • 这样做,但这与任何良好的编程实践都背道而驰。在巴西,我们称之为“甘比亚拉” 评论 2022年7月5日19:03
842

在ECMAScript 6中有对象分配方法,该方法将所有可枚举自身属性的值从一个对象复制到另一个对象。例如:

var x={myProp:“值”};var y=对象赋值({},x);

但要注意这是一份浅薄的副本-嵌套对象仍作为引用进行复制。

0
815

使用jQuery,您可以浅拷贝具有延伸:

var copiedObject=jQuery.extend({},originalObject)

对的后续更改复制的对象不会影响原始对象反之亦然。

或者制作深度复制:

var copiedObject=jQuery.extend(true,{},originalObject)
0
330

MDN公司:

  • 如果需要浅层复制,请使用对象.assign({},a)
  • 对于“深层”复制,请使用JSON.parse(JSON.stringify(a))

不需要外部库,但您需要检查浏览器兼容性优先.

1
  • 当对象JSON.parse(JSON.stringify(a))中有函数时,就会出现问题
    – 托什
    评论 2022年7月29日12:20
149

一种在一行代码中克隆Javascript对象的优雅方法

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

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

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

阅读更多。。。

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

if(!Object.assign){Object.defineProperty(Object,'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];}}}返回;}});}
2
  • 57
    这只会执行一次浅层次的“克隆” 评论 2016年7月25日13:27
  • 我很难学会objA=objB;会引起各种头痛。这似乎解决了问题,至少现在是这样。。。 评论 2022年4月17日23:38
143

有很多答案,但没有一个提到对象.创建来自ECMAScript 5,这当然不会给您提供精确的副本,但会将源代码设置为新对象的原型。

因此,这不是问题的确切答案,但它是一个单线解决方案,因此很优雅。它最适合2种情况:

  1. 这种继承在哪里有用(呸!)
  2. 源对象不会被修改,因此这两个对象之间的关系没有问题。

例子:

变量foo={a:1};var bar=Object.create(foo);foo.a;//1bar.a;//1foo.a=2;bar.a;//2-原型已更改bar.a=3;foo.a;//仍然是2,因为设置bar.a使其成为“自己的”属性

为什么我认为这个解决方案更好?它是本地的,因此没有循环,没有递归。但是,较旧的浏览器需要polyfill。

1
  • 117
    这是原型遗传,而不是克隆。这些是完全不同的事情。新对象没有任何自己的属性,它只指向原型的属性。克隆的目的是创建一个新的对象,该对象不引用其他对象中的任何属性。
    – 第13天
    评论 2014年1月16日16:18
101

互联网上的大多数解决方案都存在几个问题。所以我决定做一个跟进,包括为什么接受的答案不应该被接受。

启动情况

我想深拷贝Java脚本对象它的所有孩子和他们的孩子等等。但由于我不是一个正常的开发人员,我的对象正常的 属性,圆形构筑物甚至嵌套对象.

因此,让我们创建一个圆形结构和a嵌套对象第一。

函数Circ(){this.me=这个;}函数嵌套(y){this.y=y;}

让我们把所有的东西放在一起对象命名.

变量a={x: “a”,circ:new circ(),嵌套:new nested('a')};

接下来,我们想复制到名为的变量中b条并使其变异。

变量b=a;b.x=“b”;b.嵌套。y=“b”;

你知道这里发生了什么,因为如果不是这样,你甚至不会回答这个伟大的问题。

控制台.log(a,b);-->对象{x: “b”,circ:圆形{我:圆形{…}},嵌套:嵌套{y: “b”}}b-->对象{x: “b”,circ:圆形{我:圆形{…}},嵌套:嵌套{y: “b”}}

现在让我们找到一个解决方案。

JSON格式

我第一次尝试使用JSON格式.

var b=JSON.parse(JSON.stringify(a));b.x=“b”;b.nested.y=“b”;

不要在这上面浪费太多时间,你会得到TypeError:将循环结构转换为JSON.

递归副本(公认的“答案”)

让我们看一下公认的答案。

函数cloneSO(obj){//处理3个简单类型,null或未定义如果(null==obj||“object”!=typeofobj)返回obj;//处理日期if(obj实例日期){var copy=新日期();copy.setTime(obj.getTime());返回副本;}//句柄数组if(obj实例数组){var副本=[];for(var i=0,len=obj.length;i<len;i++){copy[i]=克隆SO(obj[i]);}返回副本;}//句柄对象if(obj对象实例){var-copy={};for(obj中的var属性){如果(obj.hasOwnProperty(attr))copy[attr]=cloneSO(obj[attr]);}返回副本;}throw new Error(“无法复制obj!不支持其类型。”);}

看起来不错,嗯?它是对象的递归副本,还处理其他类型,如日期,但这不是一项要求。

var b=克隆SO(a);b.x=“b”;b.nested.y=“b”;

递归和圆形构筑物不能很好地合作。。。RangeError:超出最大调用堆栈大小

本机解决方案

在与我的同事争论后,我的老板问我们发生了什么,他发现一个简单的解决方案在谷歌搜索之后。它被称为对象.创建.

var b=对象创建(a);b.x=“b”;b.nested.y=“b”;

这个解决方案在一段时间前被添加到Javascript中,甚至可以处理圆形结构.

控制台.log(a,b);-->对象{x: “a”,circ:圆形{我:圆形{…}},嵌套:嵌套{y: “b”}}b-->对象{x: “b”,circ:圆形{我:Circ{…}},嵌套:嵌套{y: “b”}}

……你看,它不适用于内部的嵌套结构。

天然溶液用聚乙烯填料

有一种聚乙烯填充物对象.创建在旧浏览器中,就像IE 8一样。它有点像Mozilla推荐的,当然,它并不完美,导致的问题与本机解决方案.

函数F(){};函数克隆PF(o){F.原型=o;返回新的F();}var b=克隆PF(a);b.x=“b”;b.nested.y=“b”;

我已经放好了F类超出范围,以便我们可以查看运算符告诉我们。

控制台.log(a,b);-->对象{x: “a”,circ:圆形{我:圆形{…}},嵌套:嵌套{y: “b”}}b-->F{x: “b”,circ:圆形{我:圆形{…}},嵌套:嵌套{y: “b”}}控制台.log(类型a,类型b);-->对象b-->对象console.log(对象的一个实例,对象的b个实例);a-->正确b-->真console.log(F的一个实例,F的b个实例);a-->假b-->正确

问题与本机解决方案,但输出稍差。

更好(但不是完美)的解决方案

当我四处寻找时,我发现了一个类似的问题(在Javascript中,执行深度复制时,由于属性为“this”,如何避免循环?)但有更好的解决方案。

功能克隆DR(o){const gdcc=“__getDeepCircularCopy__”;if(o!==对象(o)){返回o;//原始值}var集合=o中的gdcc,缓存=o[gdcc],结果;if(set&&typeofcache==“函数”){return cache();}//其他o[gdcc]=function(){返回结果;};//覆盖if(o数组实例){结果=[];对于(var i=0;i<o.length;i++){结果[i]=克隆DR(o[i]);}}其他{结果={};for(o中的var属性)if(prop!=gdcc)结果[prop]=克隆DR(o[prop]);else if(设置)result[prop]=克隆DR(缓存);}if(设置){o[gdcc]=缓存;//重置}其他{删除o[gdcc];//再次取消设置}返回结果;}var b=克隆DR(a);b.x=“b”;b.嵌套。y=“b”;

让我们看看输出。。。

控制台.log(a,b);-->对象{x: “a”,circ:对象{me:对象{…}},嵌套:对象{y: “a”}}b-->对象{x: “b”,circ:对象{me:对象{…}},嵌套:对象{y: “b”}}控制台.log(类型a,类型b);-->对象b-->对象console.log(对象的实例,对象的实例);a-->正确b-->正确console.log(F的一个实例,F的另一个实例);a-->假b-->假

需求是匹配的,但仍然存在一些较小的问题,包括更改实例属于嵌套的电路控制器对象.

共享一片叶子的树的结构不会被复制,它们将成为两片独立的叶子:

[对象][对象]/    \                       /    \/      \                     /      \|/_      _\|                 |/_      _\|  [对象][对象]===>[对象][Object]\        /                 |           |\      /                  |           |_\|  |/_                 \|/         \|/[对象][对象][Object]

结论

使用递归和缓存的最后一个解决方案可能不是最好的,但它是真实的对象的深度复制。它处理简单属性,圆形构筑物嵌套对象,但它会在克隆时弄乱它们的实例。

J小提琴

6
  • 12
    所以结论是要避免这个问题:)
    – 米库斯
    评论 2014年10月23日11:39
  • @米库斯,直到真实的是的,它不仅涵盖了基本用例。 评论 2014年10月23日14:41
  • 2
    对上述解决方案进行了合理分析,但作者得出的结论表明,这个问题没有解决方案。 评论 2016年8月16日16:53
  • 2
    遗憾的是JS没有包含原生克隆功能。
    – 10万
    评论 2016年11月14日14:53
  • 1
    在所有最重要的答案中,我觉得这接近正确的答案。
    – KTU公司
    评论 2017年5月15日4:24
80

如果您可以使用浅拷贝,underscore.js库有一个克隆方法。

y=克隆(x);

或者您可以将其扩展为

copiedObject=_.extend({},originalObject);
1
  • 为了快速开始使用lodash,我建议学习npm、Browserify和lodash。我让clone使用“npm I--save lodash.clone”,然后使用“var clone=require('lodash.colone');”为了满足工作需要,您需要类似browserify的东西。一旦你安装了它并了解了它的工作原理,你将使用“browserify yourfile.js>bundle.js;每次运行代码时启动chromeindex.html(而不是直接进入chrome)。这会将您的文件和您需要的所有文件从npm模块收集到bundle.js中。不过,使用Gulp可能可以节省时间并自动执行此步骤。
    – 用户11104582
    评论 2019年4月9日19:43
78

好 啊,假设下面有一个对象,您想克隆它:

设obj={a:1,b:2,c:3}//欧洲标准6

varobj={a:1,b:2,c:3}//欧洲标准5

答案主要取决于ECMA脚本您在中使用欧洲标准6+,您只需使用对象分配要进行克隆,请执行以下操作:

let cloned=Object.assign({},obj)//新{a:1,b:2,c:3};

或使用如下扩散运算符:

让克隆={…obj}//新{a:1,b:2,c:3};

但如果你使用欧洲标准5,您可以使用几种方法,但JSON.stringify格式,只需确保不使用大数据块进行复制,但在许多情况下,这可能是一种单行方便的方式,例如:

let cloned=JSON.parse(JSON.stringify(obj));//新{a:1,b:2,c:3};,可以很方便,但避免反复使用大块数据
4
  • 你能举个例子吗大量数据将等同于?10万桶?100毫巴?谢谢! 评论 2018年9月14日11:04
  • 是的,@user1063287,基本上数据越大,性能越差。。。所以这真的取决于,而不是kb、mb或gb,更多的是你想做多少次。。。而且它对函数和其他东西也不起作用。。。 评论 2019年3月14日9:58
  • 对象分配进行浅层复制(就像传播一样,@Alizera) 评论 2019年5月29日13:42
  • 您不能使用let-in es5:^)@Alireza
    – 温布尔
    评论 2020年5月1日14:56
62

2020年7月6日更新

有三(3)种方法可以在JavaScript中克隆对象。由于JavaScript中的对象是引用值,因此不能简单地使用=进行复制。

方法如下:

const food={食物:‘苹果’,饮料:‘牛奶’}// 1. 使用“排列”// ------------------{…食物}// 2. 使用“Object.assign”// ------------------对象.assign({},食物)// 3. “JSON”// ------------------JSON.parse(JSON.stringify(食物))//结果://{食物:苹果,饮料:牛奶}

这可以作为参考摘要。

7
  • 这为这个问题添加了什么新的/独特的信息? 评论 2020年7月6日8:24
  • 10
    这个JSON格式方法将删除对象的任何方法 评论 2020年7月6日8:25
  • 从一个对象创建一个字符串,然后将该字符串解析为另一个对象,只是为了复制该对象,这是一种Monty Python的编程风格:-D 评论 2021年1月10日14:24
  • 1
    这仅适用于对象文字和可以这样表示的对象,但用于OO语言中遇到的通用“对象”。这似乎是OP所要求的,所以这没关系,但它并不是每种对象的通用解决方案。
    – 巴特
    评论 2021年1月31日12:34
  • 对于具有层次结构的对象,即嵌套对象,扩展运算符和Object.assign失败。JSON.parse/stringify可以工作,但如上所述,它不会复制方法。
    – i代码
    评论 2021年3月31日17:40
47

一个特别不雅观的解决方案是使用JSON编码对没有成员方法的对象进行深度复制。方法是对目标对象进行JSON编码,然后对其进行解码,就可以得到所需的副本。您可以根据需要多次解码以制作任意数量的副本。

当然,函数不属于JSON,因此这只适用于没有成员方法的对象。

这种方法非常适合我的用例,因为我将JSON blob存储在键值存储中,当它们作为对象在JavaScript API中公开时,每个对象实际上都包含对象原始状态的副本,这样我们就可以在调用方改变公开对象后计算增量。

var对象1={键:“值”};var对象2=对象1;object2=JSON.stringify(object1);object2=JSON.parse(object2);object2.key=“更改”;console.log(对象1);//返回值
4
  • 为什么函数不属于JSON?我曾多次看到它们被转换为JSON。。。
    – 
    评论 2009年10月29日20:37
  • 6
    函数不是JSON规范的一部分,因为它们不是一种安全(或智能)的数据传输方式,而JSON正是为此而设计的。我知道Firefox中的原生JSON编码器只是忽略了传递给它的函数,但我不确定其他人的行为。 评论 2009年10月30日10:27
  • 1
    @标记:{“foo”:函数(){return 1;}}是文字构造的对象。 评论 2012年8月14日1:58
  • @abarnet函数不是数据。“函数文字”用词不当,因为函数可以包含任意代码,包括赋值和各种“不可序列化”的东西。
    – 已弃用
    评论 2013年4月30日10:38
43

您只需使用分散财产复制不带引用的对象。但要小心(请参阅注释),“副本”仅位于最低对象/数组级别。嵌套属性仍然是引用!


完整克隆:

设x={a:'value1'}设x2={…x}//=>在没有引用的情况下进行变异:x2.a=“值2”console.log(x.a)//=>“value1”

使用第二级引用进行克隆:

常量y={a:{b:'value3'}}常数y2={…y}//=>嵌套对象仍然是引用:y2.a.b=“值4”console.log(y.a.b)//=>“值4”

实际上,JavaScript本身并不支持深度克隆。使用实用函数。例如Ramda:

http://ramdajs.com/docs/#clone

  • 1
    这不起作用。。。如果x是数组,例如x=['ab','cd',…],它可能会起作用 评论 2016年4月14日8:23
  • 4
    这是可行的,但请记住,这是一个浅副本,因此任何对其他对象的深度引用都是引用!
    – 兔八哥
    评论 2016年5月17日13:01
  • 部分克隆也可以这样进行:const first={a:“foo”,b:“bar”};常量秒={…{a}=first} 评论 2018年8月13日17:37
40
const objClone={…obj};

请注意嵌套对象仍在复制作为参考。

4
  • 2
    感谢您提示嵌套对象仍将作为引用进行复制!调试代码时,我几乎疯了,因为我修改了“克隆”上的嵌套属性,但原始属性被修改了。 评论 2019年1月2日23:32
  • 这是ES2016,而不是2018,我们给出了这个答案两年前. 评论 2019年6月13日23:37
  • 那么如果我也想要嵌套属性的副本怎么办 评论 2019年9月4日8:12
  • @SunilGarg要复制嵌套属性,也可以使用const objDeepClone=JSON.parse(JSON.stringify(obj)); 评论 2019年9月6日13:45
30

根据本文:如何在Javascript中复制数组和对象作者:Brian Huisman:

Object.prototype.clone=函数(){var newObj=(此数组实例)?[] : {};for(此处为var i){如果(i==“克隆”)继续;if(此[i]&&此[i]的类型==“对象”){newObj[i]=此[i].clone();}else newObj[i]=此[i]}return newObj;};
6
  • 4
    这很接近,但对任何对象都不起作用。尝试使用此克隆Date对象。并非所有属性都是可枚举的,因此它们不会全部显示在for/in循环中。
    – A.利维
    评论 2009年4月8日4:17
  • 像这样添加到对象原型对我来说破坏了jQuery。即使我重命名为clone2。 评论 2012年8月27日23:19
  • @iPadDeveloper2011上面的代码中有一个错误,它创建了一个名为“i”的全局变量(用于此中的i),而不是“(用于此的var i)”。我有足够的业力来编辑和修复它,所以我做到了。 评论 2012年9月22日22:09
  • 2
    @卡尔文:这应该创建一个非枚举属性,否则“clone”将出现在“for”循环中。 评论 2012年10月1日10:54
  • 为什么不是var copiedObj=对象创建(obj);也很好吗?
    – 丹·P。
    评论 2014年4月12日20:16
29

对于使用AngularJS的用户,还可以使用直接方法克隆或扩展此库中的对象。

var目的地=角度复制(源);

角度拷贝(源,目标);

更多angular.copy文档...

1
  • 这是一份深度拷贝供参考。 评论 2014年9月19日10:27
27
功能克隆(obj){if(obj==null||typeof(obj)!='对象')返回对象;var temp=新对象构造函数();for(obj中的var键)temp[key]=克隆(obj[key]);返回温度;}
5
  • 11
    这个答案很接近,但不太正确。如果尝试克隆Date对象,则不会获得相同的日期,因为调用Date构造函数会使用当前日期/时间初始化新的Date。该值不可枚举,也不会被for/in循环复制。
    – A.利维
    评论 2009年4月8日4:21
  • 不完美,但适合那些基本情况。例如,允许简单克隆可以是基本Object、Array或String的参数。 评论 2013年11月1日20:36
  • 投票赞成使用正确调用构造函数新的。公认的答案不是。 评论 2015年6月14日6:29
  • 在节点上工作!仍然左侧的引用链接 评论 2017年7月15日22:38
  • 递归思想很棒。但如果值是数组,它会起作用吗?
    – Q10维京
    评论 2019年12月30日11:32
26

A.Levy的回答几乎已经完成,下面是我的一点贡献:有一种方法可以处理递归引用,请参阅此行

如果(this[attr]==this)copy[attr]=copy;

如果对象是XMLDOM元素,则必须使用克隆节点相反

if(this.cloneNode)返回this.cloeNode(true);

受A.Levy详尽研究和Calvin原型方法的启发,我提供了以下解决方案:

Object.prototype.clone=函数(){if(this.cloneNode)返回this.cloeNode(true);var copy=这个数组实例?[] : {};for(此处为var-attr){if(this[attr]==“function”|this[attr]==null||!this[Atttr].clone的类型)copy[attr]=此[attr];否则,如果(this[attr]==this)copy[attr]=copy;else copy[attr]=这个[attr].clone();}返回副本;}Date.prototype.clone=函数(){var copy=新日期();copy.setTime(this.getTime());返回副本;}编号.原型.克隆=布尔型.原型.克隆=String.prototype.clone=函数(){返回此;}

另请参阅安迪·伯克的回答。

1
  • Date.prototype.clone=函数(){return new Date(+this)};
    – 罗布·G
    评论 2014年12月2日12:20
26

性能

今天2020.04.30,我在Chrome v81.0、Safari v13.1和Firefox v75.0上对所选解决方案进行了测试。

我关注复制DATA的速度(对象具有简单类型字段,而不是方法等)。解决方案A-I只能生成浅拷贝,解决方案J-U可以生成深拷贝。

浅层复制结果

  • 解决方案{…对象}(A) 在chrome和firefox上速度最快,在safari上速度中等
  • 解决方案基于对象分配(B) 在所有浏览器上都很快
  • jQuery(E)和lodash(F、G、H)解决方案是中等/相当快的
  • 解决方案JSON.parse/stringify格式(K) 速度很慢
  • 解决方案D和U在所有浏览器上都很慢

在此处输入图像描述

深度复制的结果

  • 解决方案Q在所有浏览器上都是最快的
  • jQuery(L)和lodash(J)速度适中
  • 解决方案JSON.parse/stringify格式(K) 速度很慢
  • 解决方案U在所有浏览器上速度最慢
  • lodash(J)和Chrome上1000级深目标的解决方案U崩溃

在此处输入图像描述

细节

对于选择的解决方案:A类 B类C(我的)D类 E类 F类 G公司 H(H) J型 K(K) L(左) M(M) N个 O(运行) P(P) R(右) S公司 T型 U型,我进行了4次测试

测试中使用的对象显示在下面的代码片段中

设obj_ShallowSmall={字段0:假,字段1:真,字段2:1,字段3:0,字段4:空,字段5:[],字段6:{},字段7:“text7”,field8:“文本8”,}让obj_DeepSmall={级别0:{级别1:{第2级:{第3级:{级别4:{第5级:{级别6:{第7级:{第8级:{第9级:[[[[abc]]]]],}}}}}}}}},};让obj_ShallowBig=Array(1000).fill(0).reduce((a,c,i)=>(a['field'+i]=getField(i),a),{});让obj_DeepBig=genDeepObject(1000);// ------------------//显示对象// ------------------console.log('obj_ShallowSmall:',JSON.stringify(obj_ShollowSmall));console.log('obj_DepSmall:',JSON.stringify(obj_DeepSmall));console.log('obj_ShallowBig:',JSON.stringify(obj_ShollowBig));console.log('obj_DeepBig:',JSON.stringify(obj_DepBig));// ------------------//帮助// ------------------函数getField(k){设i=k%10;如果(i==0)返回false;如果(i==1)返回true;如果(i==2),则返回k;如果(i==3),则返回0;如果(i==4)返回null;如果(i==5)返回[];如果(i==6)返回{};如果(i>=7)返回“text”+k;}函数genDeepObject(N){//生成:{level0:{level1:{…levelN:{end:[[[…N次…['abc']…]]}}}…}}设obj={};设o=obj;设arr=[];设a=arr;for(设i=0;i<N;i++){o['level'+i]={};o=o['水平'+i];设aa=[];a.推动(aa);a=aa;}a[0]='abc';o['end']=arr;返回对象;}

下面的片段介绍了经过测试的解决方案,并显示了它们之间的差异

功能A(obj){返回{…obj}}功能B(obj){return Object.assign({},obj);}功能C(obj){return Object.keys(obj).reduce((a,c)=>(a[c]=obj[c],a),{})}功能D(obj){让copyOfObject={};Object.defineProperties(copyOfObject、Object.getOwnPropertyDescriptors(obj));return copyOfObject;}功能E(obj){return jQuery.extend({},obj)//浅层}函数F(obj){返回克隆(obj);}函数G(obj){返回克隆(obj,true);}函数H(obj){return_.extend({},obj);}函数I(对象){if(null==obj||“object”!=typeof obj)返回obj;var copy=对象构造函数();for(obj中的var属性){如果(obj.hasOwnProperty(attr))copy[attr]=obj[attr];}返回副本;}函数J(obj){return _.cloneDeep(obj,true);}函数K(obj){return JSON.parse(JSON.stringify(obj));}函数L(obj){return jQuery.extend(true,{},obj)//深度}函数M(obj){if(obj==null||typeof(obj)!='对象')返回对象;var temp=新对象构造函数();for(obj中的var键)温度[键]=M(对象[键]);返回温度;}函数N(obj){让EClone=函数(obj){var newObj=(数组的obj实例)?[] : {};for(obj中的var i){如果(i==“EClone”)继续;if(obj[i]&&对象类型[i]==“对象”){newObj[i]=克隆(对象[i]);}else newObj[i]=对象[i]}return newObj;};return EClone(obj);};函数O(obj){如果(obj==null||typeofobj!=“object”)返回obj;if(obj.constructor!=对象&&obj.constructor!=数组)返回obj;if(obj.constructor==日期||obj.confructor==RegExp||obj.construtor==函数||obj.constructor==字符串||obj.constructor==数字||obj.constructionor==布尔值)返回新的obj.constructor(obj);let to=新obj.constructor();for(对象中的变量名称){to[name]=类型to[name]=“未定义”?O(obj[name],null):到[name];}返回;}函数P(obj){功能克隆(目标、源){for(让键入源代码){//使用getOwnPropertyDescriptor而不是source[key]来防止触发setter/getter。let描述符=Object.getOwnPropertyDescriptor(源,键);if(字符串的描述符.value实例){target[key]=新字符串(descriptor.value);}else if(数组的描述符.value实例){target[key]=克隆([],descriptor.value);}else if(对象的描述符.value实例){let prototype=Reflect.getPrototypeOf(descriptor.value);让cloneObject=克隆({},descriptor.value);Reflect.setPrototypeOf(cloneObject,prototype);target[key]=克隆对象;}其他{Object.defineProperty(目标、键、描述符);}}let prototype=Reflect.getPrototypeOf(源);Reflect.setPrototypeOf(目标,原型);返回目标;}返回克隆({},obj);}函数Q(obj){var复制;//处理3个简单类型,null或未定义如果(null==obj||“object”!=typeofobj)返回obj;//处理日期if(obj实例日期){copy=新日期();copy.setTime(obj.getTime());返回副本;}//句柄数组if(obj实例数组){copy=[];对于(var i=0,len=obj.length;i<len;i++){copy[i]=Q(obj[i]);}返回副本;}//句柄对象if(对象的obj实例){副本={};for(obj中的var属性){如果(obj.hasOwnProperty(attr))copy[attr]=Q(obj[attr]);}返回副本;}throw new Error(“无法复制obj!不支持其类型。”);}功能R(对象){const gdcc=“__getDeepCircularCopy__”;if(obj!==对象(obj)){返回对象;//原始值}var集合=对象中的gdcc,缓存=obj[gdcc],结果;if(set&&typeofcache==“函数”){return cache();}//其他obj[gdcc]=function(){返回结果;};//覆盖if(obj实例数组){结果=[];for(var i=0;i<obj.length;i++){结果[i]=R(对象[i]);}}其他{结果={};for(obj中的var属性)if(prop!=gdcc)结果[prop]=R(obj[prop]);else if(设置)结果[prop]=R(缓存);}if(设置){obj[gdcc]=缓存;//重置}其他{删除对象[gdcc];//再次复位}返回结果;}函数S(obj){const缓存=新WeakMap();//新旧参考地图函数副本(对象){if(typeof object!==“object”||对象===空||HTMLElement的对象实例)返回对象;//原语值或HTMLElementif(对象实例日期)return new Date().setTime(object.getTime());if(RegExp的对象实例)return new RegExp(object.source,object.flags);if(cache.has(对象))return-cache.get(对象);const result=数组的对象实例?[] : {};cache.set(对象,结果);//在递归开始之前存储对对象的引用if(数组的对象实例){for(对象常数){结果推送(复制(o));}返回结果;}const keys=Object.keys(对象);for(键的常量键)结果[key]=副本(对象[key]);返回结果;}返回副本(obj);}功能T(obj){var clonedObjectsArray=[];var originalObjectsArray=[]//用于在完成时删除唯一IDvar next_objid=0;函数objectId(obj){如果(obj==null)返回null;if(obj.__obj_id==未定义){obj.__obj_id=下一个objid++;原始对象数组[对象.__obj_id]=对象j;}返回对象__obj_id;}函数cloneRecursive(obj){如果(null==obj||typeofobj==“string”||typeof obj===“number”||typeof obj==“boolean”)返回obj;//处理日期if(obj实例日期){var copy=新日期();copy.setTime(obj.getTime());返回副本;}//手柄阵列if(obj实例数组){var副本=[];对于(var i=0;i<obj.length;++i){copy[i]=克隆递归(obj[i]);}返回副本;}//处理对象if(obj对象实例){if(clonedObjectsArray[对象ID(obj)]!=未定义)return clonedObjectsArray[对象ID(obj)];var复制;if(obj instanceof Function)//句柄函数copy=function(){return obj.apply(this,arguments);};其他的copy={};clonedObjectsArray[objectId(obj)]=复制;for(obj中的var属性)if(属性!=“__obj_id”&&obj.hasOwnProperty(属性))copy[attr]=克隆递归(obj[attr]);返回副本;}       throw new Error(“无法复制obj!不支持其类型。”);}var cloneObj=克隆递归(obj);//删除唯一的idfor(var i=0;i<originalObjectsArray.length;i++){删除原始对象阵列[i]__对象id;};return cloneObj;}功能U(obj){/*按值而不是按引用深度复制对象,异常:`代理`*/const seen=新WeakMap()返回克隆(obj)函数defineProp(object,key,descriptor={},copyFrom={}){常量{configurable:configurable,writable:writable}=Object.getOwnPropertyDescriptor(对象,键)||{可配置:true,可写:true}const test=_configuratable//可以重新定义属性&&(_writeable==未定义||_writeable)//可以分配给属性if(!test | | arguments.length<=2)返回测试const basisDesc=对象.getOwnPropertyDescriptor(copyFrom,key)||{configurable:true,writable:true}//自定义…|| {}; // …或保留为本机默认设置[“get”,“set”,“value”,“writable”,“enumerable”,“configurable”].forEach(属性=>描述符[attr]===未定义&&(描述符[attr]=basisDesc[attr]))常量{get,set,value,writable,enumerable,configurable}=描述符return Object.defineProperty(对象,键{可枚举、可配置。。。获取集合? {get,set}//访问器描述符:{value,writable}//数据描述符})}函数克隆(对象){if(object!==object(object))返回对象/*--检查对象是否属于基元数据类型*/if(节点的对象实例)返回object.cloneNode(true)/*--克隆DOM树*/let _ object//对象的克隆开关(object.constructor){case数组:case对象:_object=克隆对象(对象)打破案例日期:_object=新日期(+object)打破case函数:const fnStr=字符串(对象)_object=新函数(“return”+(/^(?!函数|[^{]+?=>)[^(]+?\(/.test(fnStr))? “函数”:“”)+fnStr)()copyPropDescs(_object,object)打破案例RegExp:_object=新RegExp(对象)打破违约:switch(Object.prototype.toString.call(Object.constructor)){////词干来源:case“[object Function]”://`class`case“[object Undefined]”://`object.create(null)`_object=克隆对象(对象)打破default://`代理`_object=对象}}返回对象(_O)}函数cloneObject(对象){if(seen.has(object))返回seen.get(objects)/*--处理递归引用(循环结构)*/const_object=数组.isArray(对象)? []:Object.create(Object.getPrototypeOf(Object))/*--分配[[原型]]进行继承*/seen.set(对象,对象)/*--使`_object`成为`object`的关联镜像*/Reflect.ownKeys(object).forEach(key=>)defineProp(_object,key,{value:克隆(object[key])},object))返回对象(_O)}函数copyPropDescs(目标,源){Object.defineProperties(目标,Object.getOwnPropertyDescriptors(源))}}// ------------------------//测试属性// ------------------------console.log(`shallow-deph func circ未定义日期RegExp bigInt`)对数(A);对数(B);对数(C);对数(D);对数(E);对数(F);对数(G);对数(H);日志(I);对数(J);log(K);对数(L);对数(M);对数(N);对数(O);对数(P);对数(Q);对数(R);对数(S);对数(T);对数(U);console.log(`shallow deep func circ未定义日期RegExp bigInt----图例:浅层-解决方案创建浅层副本deep-解决方案创建deep拷贝func-解决方案复制函数circ解决方案可以复制具有循环引用的对象undefined-具有未定义值的解决方案复制字段日期-解决方案可以复制日期RegExp-解决方案可以使用正则表达式复制字段bigInt-解决方案可以复制bigInt`)// ------------------------//助手函数// ------------------------函数deepCompare(obj1,obj2){return JSON.stringify(obj1)===JSON.stringify(obj2);}函数getCase(){//纯数据案例返回{undef:未定义,bool:true,num:1,str:“txt1”,e1:null,e2:[],e3:{},e4:0,e5:false,arr:[false,2,“txt3”,空,[],{},[真,4,“txt5”,空,[],{},[真,6,“txt 7”,空,{bool:true,num:8,str:“txt9”,e1:null,e2:[],e3:{},e4:0,e5:false}],{bool:true,num:10,str:“txt11”,e1:null,e2:[],e3:{},e4:0,e5:false}], 对象:{bool:true,num:12,str:“txt13”,e1:null,e2:[],e3:{},e4:0,e5:false,arr:[true,14,“txt15”,null,[],{}],对象:{bool:true,num:16,str:“txt17”,e1:null,e2:[],e3:{},e4:0,e5:false,arr:[真,18,“txt19”,空,[],{}],obj:{bool:true,num:20,str:“txt21”,e1:null,e2:[],e3:{},e4:0,e5:false}} } };}功能检查(组织、副本、字段、newValue){copy[field]=newValue;return deepCompare(org,copy);}功能测试Func(f){设o={a:1,有趣:(i,j)=>i+j};设c=f(o);让val=假尝试{val=c.fun(3,4)==7;}捕获(e){}返回值;} 功能测试Circ(f){函数Circ(){this.me=这个;}var o={x: “a”,circ:new circ(),obj_circ:空,};o.obj_circ=o;设val=false;尝试{设c=f(o);val=(o.obj_circ==o)&&(o.circ==o.circ.me);}捕获(e){}返回值;} 功能测试RegExp(f){设o={回复:/a[0-9]+/,};设val=false;尝试{设c=f(o);val=(字符串(c.re)==字符串(/a[0-9]+/));}捕获(e){}返回val;}功能测试日期(f){设o={date:new date(),};设val=false;尝试{设c=f(o);val=(+new Date(c.Date)==+new日期(o.Date));}捕获(e){}返回值;}功能测试BigInt(f){设val=false;尝试{设o={大:123n,};设c=f(o);val=o.big==c.big;}捕获(e){}返回值;}函数日志(f){设o=getCase();//原始对象让oB=getCase();//“备份”用于浅层有效测试设c1=f(o);//副本1供参考设c2=f(o);//副本2用于测试浅层值设c3=f(o);//副本3用于测试深度值让is_proper_copy=深度比较(c1,o);//应该是真的//浅层变化让testShallow=[['bool',false],['num',666],[['str','xyz'],[arr',[]],[`obj',{}]].reduce((acc,curr)=>acc&&检查(c1,c2,curr[0],curr[1]),true);//应为true(原始对象不应更改浅域)让is_valid=deepCompare(o,oB);//深度测试(引入一些变化)如果(c3.arr[6])c3.arr[6][7].num=777;让diff_shallow=!测试浅层;//应该是真的(复制了浅域)让diff_deep=!深度比较(c1,c3);//应该是真的(深场被复制)let can_copy_functions=测试函数(f);让can_copy_circular=testCirc(f);让can_copy_regexp=testRegExp(f);让can_copy_date=测试日期(f);让can_copy_bigInt=测试bigInt(f);让has_undefined=c1中的'undef';//复制具有未定义值的字段?设is_ok=is_valid&&is_proper_copy;设b=(bool)=>(bool+'').padEnd(5,'');//将布尔值转换为格式化字符串testFunc(f);if(is_ok){控制台.log}其他{控制台.log(`${f.name}:INVALID${is_valid}${is_proper_copy}`,{c1})}}
<script src=“https://code.jquery.com/jquery-3.5.0.min.js网址“integrity=”sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=“crossorigin=”anonymous“></script><script src=“https://cdn.jsdeliver.net/npm网址/[电子邮件保护]/lodash.min.js“></script>这段代码只展示了经过测试的解决方案,并显示了它们之间的差异(但它没有进行性能测试)

下面是Chrome用于浅大对象的示例结果

在此处输入图像描述

2
  • 如果对象包含window/globalThis或对自身的引用,或者包含Proxy,该怎么办?structuredClone-如果对象包含代理、窗口或对自身的引用,则会崩溃。其他一切-如果对象包含窗口或对其自身的引用,则会崩溃。
    – 韦罗罗
    评论 2月15日7:07
  • @weroro部分解决方案:对于具有循环引用的对象使用这个将对象串行化为字符串并取消序列化(然而,我看到“windows”对象的取消序列化存在一些错误-并非所有路径键都会被替换…请随时改进该解决方案)。然而,在这种方法中,只会复制数据,而不会复制方法/函数等。 评论 2月15日9:49
24

使用Lodash:

var y=_克隆(x,true);
2
  • 6
    天哪,重新发明克隆技术是疯狂的。这是唯一明智的答案。 评论 2013年9月11日8:48
  • 7
    我更喜欢_克隆深度(x)因为它本质上和上面一样,但读起来更好。 评论 2014年12月18日19:40
23

在ES-6中,您可以简单地使用Object.assign(…)。前任:

让obj={person:'Thor Odinson'};let clone=Object.assign({},obj);

这里有一个很好的参考:https://googlechrome.github.io/samples/object-assign-es6/

6
  • 12
    它不会深度克隆对象。
    – 八月
    评论 2017年6月1日5:31
  • 那是一份作业,不是副本。克隆。Title=“只是一个克隆”意味着obj.Title=“只是克隆”。 评论 2017年8月16日16:05
  • @忍住饥饿你错了。在浏览器的JS控制台中进行检查(让obj={person:'Thor Odinson'};let clone=Object.assign({},obj);clone.title=“Whazup”;)
    – 坍塌的
    评论 2017年9月1日11:00
  • @collapser:这正是我检查的内容,然后console.log(person)将是“Whazzup”,而不是“Thor Odinson”。请参阅8月份的评论。 评论 2017年9月1日11:40
  • 1
    @Chrome 60.0.3112.113和Edge 14.14393中不存在HoldOffHunger;August的注释不适用于对象的属性确实被克隆了。对象本身的属性值不会被克隆。
    – 拼贴画
    评论 2017年9月1日12:50
21

对克隆简单对象感兴趣:

JSON.parse(JSON.stringify(JSON_original));

资料来源:如何将JavaScript对象复制到新变量NOT中?

  • 很好,很简单。
    – 马特H
    评论 2019年5月24日19:16
  • @马特:这个答案已经给出了2012年.你看到了吗?穆罕默德,你在复制其中一个答案之前检查过现有答案吗? 评论 2019年6月13日23:40
  • 好吧,这是一种方法。ty从没想过
    – 1-14x0转
    评论 2020年4月8日21:53
20

结构化克隆

2022年更新:这个结构化克隆()Node 17、Deno 1.14和大多数主要浏览器中已经提供了全局功能(请参阅我能用吗).

可以使用HTML标准中包含的用于在域之间发送数据的相同结构化克隆机制。

const clone=structuredClone(原始);

请参见另一个答案了解更多详细信息。

1
18
let clone=Object.assign(Object.create(Object.getPrototypeOf(obj)),obj)

ES6解决方案(如果您想(浅层)克隆类实例而不仅仅是属性对象。

2
  • 这与let cloned=Object.assign({},obj)? 评论 2019年5月21日11:25
  • @ceztko时间对象是类实例,Object.assign()不克隆例如类方法(因为它们不可枚举)。 评论 2021年1月2日19:58
17

您可以使用一行代码克隆一个对象并从上一个对象中删除任何引用。只需做:

var obj1={text:'moo1'};var obj2=对象创建(obj1);//创建不带引用的新克隆obj2.text='moo2';//仅更新obj2的文本属性控制台.log(obj1,obj2);//输出:obj1:{text:'moo1'},obj2:{text:`moo2'}

对于当前不支持Object.create的浏览器/引擎,可以使用此polyfill:

//Polyfill Object.create(如果不存在)if(!Object.create){Object.create=函数(o){var F=函数(){};F.原型=o;返回new F();};}
7
  • 1
    +1对象.create(…)看来这绝对是一条路。 评论 2014年6月30日14:49
  • 完美的答案。也许你可以为对象.hasOwnProperty? 这样人们就知道如何防止搜索原型链接。 评论 2014年8月9日12:30
  • 工作良好,但polyfill在哪些浏览器中工作? 评论 2014年10月9日13:25
  • 12
    这是用一个obj1作为原型来创建obj2。只有当你在跟踪文本obj2中的成员。您不是在制作副本,只是在obj2上找不到成员时顺从原型链。 评论 2014年10月31日21:25
  • 2
    这并不是“没有引用”创建它,它只是将引用移动到原型。它仍然是一个参考。如果原始属性发生更改,则“克隆”中的原型属性也会发生更改。这根本不是克隆。 评论 2016年5月9日0:01
15

老问题的新答案!如果您有幸将ECMAScript 2016(ES6)与Spread语法,这很容易。

keepMeTheSame={第一个:“我!”,第二个:“你!”};克隆={…keepMeTheSame}

这为对象的浅层副本提供了一种干净的方法。制作深度复制,意味着为每个递归嵌套对象中的每个值创建一个新的副本,需要使用上面的重解决方案。

JavaScript不断发展。

4
  • 2
    当在对象上定义函数时,它不起作用 评论 2017年2月5日22:06
  • 据我所知,spread操作符只适用于迭代器-开发人员.mozilla.org说:var obj={“key1”:“value1”}; var数组=[…obj];//TypeError:obj不可迭代
    – 奥勒
    评论 2017年4月4日8:12
  • @Oleh因此使用`{…obj}而不是[…obj]` 评论 2017年12月5日6:17
  • @manikantgautam我以前使用Object.assign(),但现在最新的Chrome、Firefox(仍然不支持Edge和Safari)支持对象传播语法。其ECMAScript提案。。。但据我所知,巴别塔确实支持它,所以使用它可能是安全的。
    – 奥勒
    评论 2017年12月6日15:11
14

我认为有一个简单可行的答案。深度复制有两个问题:

  1. 保持属性相互独立。
  2. 并在克隆对象上保持方法的活性。

所以我认为一个简单的解决方案是首先序列化和反序列化,然后对其进行赋值以复制函数。

let deepCloned=JSON.parse(JSON.stringify(source));let merged=Object.assign({},source);Object.assign(合并,deepCloned);

虽然这个问题有很多答案,但我希望这个问题也能有所帮助。

4
  • 虽然如果我被允许进口lodash,但我更喜欢使用lodash克隆深度. 评论 2019年1月13日6:01
  • 2
    我使用的是JSON.parse(JSON.stringfy(源代码))。一直在工作。
    – 米沙
    评论 2019年2月22日15:50
  • 2
    @米莎,这样你就会错过功能了。“作品”一词有很多含义。 评论 2019年2月23日5:26
  • 请记住,按照我之前提到的方式,只有第一层的功能会被复制。因此,如果我们有一些对象在彼此内部,那么唯一的方法就是逐个字段递归地复制。 评论 2019年2月23日5:29
14

对于深度复制和克隆,JSON.stringify然后JSON.parse对象:

obj={a:0,b:{c:0}};let deepClone=JSON.parse(JSON.stringify(obj));目标a=5;对象b.c=5;console.log(JSON.stringify(deepClone));//{a:0,b:{c:0}
1
  • 非常聪明。。。这种方法有什么缺点吗? 评论 2019年7月21日1:56
11

(以下主要是@马西耶·布科夫斯基, @A.利维, @扬·图罗, @雷杜的答案,以及@利维罗伯茨, @罗布·G的评论,非常感谢他们!!!)

深度复制? — 对!(大部分);
浅拷贝? — 不!(除代理).

我真诚地欢迎大家来测试克隆().
此外,定义提升()设计为方便快捷(重新)定义或复制任何类型的描述符。

功能

函数克隆(对象){/*按值而不是按引用深度复制对象,异常:`代理`*/const seen=新WeakMap()返回克隆(对象)函数克隆(对象){if(object!==object(object))返回对象/*--检查对象是否属于基元数据类型*/if(节点的对象实例)返回object.cloneNode(true)/*--克隆DOM树*/let _object//对象的克隆开关(object.constructor){case数组:case对象:_object=克隆对象(对象)打破案例日期:_object=新日期(+object)打破case函数:_object=copyFn(对象)打破案例RegExp:_object=新RegExp(对象)打破违约:switch(Object.prototype.toString.call(Object.constructor)){////词干来源:案例“[object Function]”:switch(对象[Symbol.toStringTag]){大小写未定义:_object=克隆对象(object)//`类`打破案例“AsyncFunction”:“发电机功能”案例:案例“AsyncGeneratorFunction”:_object=copyFn(对象)打破违约:_object=对象}打破case“[object Undefined]”://`object.create(null)`_object=克隆对象(对象)打破违约:_object=object//`代理`}}返回对象(_O)}函数cloneObject(对象){if(seen.has(object))返回seen.get(objects)/*--处理递归引用(循环结构)*/const_object=数组.isArray(对象)? []:Object.create(Object.getPrototypeOf(Object))/*--分配[[原型]]进行继承*/seen.set(对象,对象)/*--使`_object`成为`object`的关联镜像*/Reflect.ownKeys(object).forEach(key=>)defineProp(_object,key,{value:clone(object[key])},object))返回对象(_O)}}函数copyPropDescs(目标,源){Object.defineProperties(目标,Object.getOwnPropertyDescriptors(源))}功能转换器FnToStr(fn){让fnStr=字符串(fn)if(fn.name.startsWith(“[”))//是符号键fnStr=fnStr.replace(/\[符号\..+?\]/,'')fnStr=/^(?!(异步)?(函数\b|[^{]+?=>)[^(]+?\(/.test(fnStr)? fnStr.replace(/^(异步)?(\*)?/, “$1功能$2”):fnStr返回fnStr}函数copyFn(fn){const newFn=新函数(`return${convertFnToStr(fn)}`)()copyPropDescs(新fn,fn)返回newFn}函数defineProp(object,key,descriptor={},copyFrom={}){常量{configurable:configurable,writable:writable}=Object.getOwnPropertyDescriptor(对象,键)||{可配置:true,可写:true}const test=_configurable//可以重新定义属性&&(_writable===未定义||_writble)//可以分配给属性if(!test | | arguments.length<=2)返回测试const basisDesc=对象.getOwnPropertyDescriptor(copyFrom,key)||{configurable:true,writable:true}//自定义…|| {}; // …或保留为本机默认设置[“get”,“set”,“value”,“writable”,“enumerable”,“configurable”].forEach(属性=>描述符[attr]===未定义&&(描述符[attr]=basisDesc[attr]))常量{get,set,value,writable,enumerable,configurable}=描述符return Object.defineProperty(对象,键{可枚举、可配置。。。获取||集? {get,set}//访问器描述符:{value,writible}//数据描述符})}

//测试

常量obj0={u: 未定义,nul:空,t: 是的,数字:9,字符串:“”,sym:符号(“Symbol”),[符号(“e”)]:数学。E、,arr:[[0],[1,2],d: 新日期(),回复:/f/g,获取g(){return 0},o:{n: 0,o: {f:函数(…参数){}}},传真:{getAccessorStr(对象){返回[].concat(。。。Object.values(Object.getOwnPropertyDescriptors(对象)).filter(desc=>desc.writable===未定义).map(desc=>对象值(desc))).filter(道具=>道具类型==“功能”).map(字符串)},f0:函数f0(){},f1:函数(){},a=>a/(a+1),f3:()=>0,f4(params){return param=>param+params},f5:(a,b)=>({c=0}={})=>a+b+c}}defineProp(obj0,“s”,{set(v){this.s=v}})defineProp(obj0.arr,“tint”,{value:{is:“non-enumerable”}})obj0.arr[0].name=“嵌套数组”设obj1=克隆(obj0)目标1.o.n=1obj1.o.o.g=函数g(a=0,b=0){return a+b}obj1.arr[1][1]=3obj1.d.setTime(+obj0.d+60*1000)obj1.arr.tin.is=“可枚举?否”obj1.arr[0].name=“嵌套arr”defineProp(obj1,“s”,{set(v){this.s=v+1}})defineProp(obj1.re,“多行”,{value:true})控制台.log(“\n\n”+“-”.repeat(2**6))console.log(“>:>:测试-常规”)console.log(“obj0:\n”,JSON.stringify(obj0))console.log(“obj1:\n”,JSON.stringify(obj1))控制台.log()控制台.log(“obj0:\n”,obj0)控制台.log(“obj1:\n”,obj1)控制台.log()控制台.log(“obj0\n”,“.arr.tint:”,obj0.arr.tint,“\n”,“.arr[0].name:”,对象0.arr[0].name)控制台.log(“obj1\n”,“.arr.tint:”,obj1.arr.tint,“\n”,“.arr[0].name:”,obj1.arr[0].name)控制台.log()console.log(“访问器类型描述符\n”,“obj0:”,obj0.f.getAccessorStr(obj0),“\n”,“obj1:”,obj1.f.getAccessorStr(obj1),“\n”,“集合(obj0&obj1).s:”,obj0.s=obj1.s=0,“\n”,“→(obj0,obj1)_s: “,对象0.s,”,“,”,对象1.s)console.log(“--obj0没有受到干扰。”)控制台.log(“\n\n”+“-”.repeat(2**6))log(“>:>:Test-更多类型的函数”)const fnsFor测试={f(_){返回},函数:_=>_,aFunc:异步_=>_,异步函数(){},async-asyncFunc(){},aFn:async函数(){},*gen(){},async*asyncGen(){},aG1:异步函数*(){},aG2:异步函数*gen(){},*[Symbol.iterator](){yield*Object.keys(this)}}console.log(Reflect.ownKeys(fnsForTest).map(k=>`${字符串(k)}:${fnsForTest[k].name}-->${字符串(fnsForTest[k])}`).join(“\n”)常量normedFnsStr=`{f: 函数f(_){return_},函数:_=>_,aFunc:异步_=>_,函数:异步函数(){},asyncFunc:异步函数asyncFunc(){},aFn:异步函数(){},gen:函数*gen(){},asyncGen:异步函数*asyncGen(){},aG1:异步函数*(){},aG2:异步函数*gen(){},[Symbol.iterator]:函数*(){yield*Object.keys(this)}}`const copiedFnsForTest=克隆(fnsForTest)console.log(“fnsForTest:”,fnsForTest)console.log(“fnsForTest(copyed):”,copyedFnsFortest)log(“fnsForTest(normedstr):”,eval(`(${normedFnsStr})`)console.log(“fnsForTest及其克隆的比较:”,Reflect.ownKeys(fnsForTest).map(k=>[k,fnsForTest[k]===复制fnsForTest[k]]))控制台.log(“\n\n”+“-”.repeat(2**6))console.log(“>:>:测试-循环结构”)obj0.o.r={}obj0.o.r.递归=obj0.oobj0.arr[1]=对象0.arrobj1=克隆(obj0)console.log(“obj0:\n”,obj0)console.log(“obj1:\n”,obj1)console.log(“清除obj0的递归:”,obj0.o.r.recursive=null,obj0.arr[1]=1)控制台.log(“obj0\n”,“.o.r:”,obj0.o.r,“\n”,“.arr:”,对象0.arr)控制台.log(“obj1\n”,“.o.r:”,obj1.o.r,“\n”,“.arr:”,obj1.arr)console.log(“--obj1没有受到干扰。”)控制台.log(“\n\n”+“-”.repeat(2**6))console.log(“>:>:测试-类”)类人员{建造师(姓名){this.name=名称}}类Boy扩展Person{}Boy.prototype.sex=“M”const boy0=新男孩boy0.abotion={运动:“太空飞行”}const boy1=克隆(boy0)boy1.hoby.sport=“超光速飞行”boy0.name=“一个”boy1.name=“neo”控制台.log(“boy0:\n”,boy0)控制台.log(“boy1:\n”,boy1)log(“boy1的原型===boy0的:”,Object.getPrototypeOf(boy1)===对象.getProtocypeOv(boy0))

工具书类

  1. 对象.create()|MDN公司
  2. 对象.defineProperties()|MDN公司
  3. 属性的可枚举性和所有权|MDN
  4. TypeError:循环对象值|MDN

使用的语言技巧

  1. 有条件地将道具添加到对象
2
  • 符号(“a”)===符号(“a”),不应该克隆(符号(“a”))使用符号(对象描述)创建新符号?或者这会不会对众所周知的符号产生太奇怪的影响? 评论 2021年7月10日15:39
  • @塞巴斯蒂安·西蒙👍 你的考虑很全面!你的最后一句话是更多对的,例如。(新地图)[Symbol.iterator](新地图)[Symbol(Symbol.iterator.description)].
    – 哦哦
    评论 2022年6月1日5:31
10

使用lodash _.cloneDeep()。

浅拷贝:lodash _.clone()

只需复制引用即可进行浅层复制。

设obj1={a: 0,b:{c: 0,电子邮箱:{f: 0个}}};设obj3=克隆(obj1);obj1.a=4;obj1.b.c=4;obj1.b.e.f=100;console.log(JSON.stringify(obj1));//{“a”:4,“b”:{“c”:4console.log(JSON.stringfy(obj3));//{“a”:0,“b”:{“c”:4,“e”:{“f”:100}}}

浅拷贝:lodash_.clone()

深度复制:lodash_.cloneDeep()

字段被取消引用:而不是对正在复制的对象的引用

设obj1={a: 0,b:{c: 0,电子邮箱:{f: 0个}}};设obj3=_.cloneDeep(obj1);obj1.a=100;obj1.b.c=100;obj1.b.e.f=100;console.log(JSON.stringify(obj1));{“a”:100,“b”:{“c”:100console.log(JSON.stringify(obj3));{“a”:0,“b”:{“c”:0

深度复制:lodash _.cloneDeep()

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