我有一个对象,x
.我想把它复制为对象y
,这样改变y
就不要修改了x
.我意识到复制从内置JavaScript对象派生的对象将导致额外的,不需要的属性.这不是问题,因为我正在复制我自己的一个文字构造的对象.
如何正确克隆JavaScript对象?
为JavaScript中的任何对象执行此操作不会简单或直接.您将遇到错误地从对象原型中拾取属性的问题,该属性应保留在原型中而不会复制到新实例.例如,如果您要添加clone
方法Object.prototype
,如某些答案所示,则需要显式跳过该属性.但是,如果还有其他额外的方法Object.prototype
,或其他中间原型,你不知道怎么办?在这种情况下,您将复制不应该的属性,因此您需要使用该hasOwnProperty
方法检测无法预料的非本地属性.
除了不可枚举的属性,当您尝试复制具有隐藏属性的对象时,您将遇到更严峻的问题.例如,prototype
是函数的隐藏属性.此外,对象的原型使用属性进行引用,该属性__proto__
也是隐藏的,并且不会通过迭代源对象属性的for/in循环进行复制.我认为__proto__
可能是Firefox的JavaScript解释器特有的,它可能在其他浏览器中有所不同,但是你可以了解它.并非一切都是可以计算的.如果您知道其名称,则可以复制隐藏属性,但我不知道有任何方法可以自动发现它.
寻求优雅解决方案的另一个障碍是正确设置原型继承的问题.如果您的源对象的原型是Object
,那么只需创建一个新的通用对象{}
将工作,但如果源的原型是其后代Object
,那么您将错过使用hasOwnProperty
过滤器跳过的原型中的其他成员,或者在原型中,但首先不是可枚举的.一种解决方案可能是调用源对象的constructor
属性来获取初始复制对象,然后复制属性,但是您仍然不会获得不可枚举的属性.例如,Date
对象将其数据存储为隐藏成员:
function clone(obj) { if (null == obj || "object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; } var d1 = new Date(); /* Executes function after 5 seconds. */ setTimeout(function(){ var d2 = clone(d1); alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString()); }, 5000);
日期字符串d1
将落后5秒d2
.使一个Date
与另一个相同的setTime
方法是调用方法,但这是特定于Date
类的.我不认为这个问题有防弹的一般解决方案,但我会很高兴出错!
当我不得不实施一般深度复制我最终通过假设我只需要复制一个普通的妥协Object
,Array
,Date
,String
,Number
,或Boolean
.最后3种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变.我进一步假设该列表中包含的6个简单类型中的任何元素Object
或者Array
也将是其中之一.这可以使用以下代码完成:
function clone(obj) { var copy; // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = clone(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }
只要对象和数组中的数据形成树结构,上述函数就可以适用于我提到的6种简单类型.也就是说,对象中的相同数据的引用不超过一个.例如:
// This would be cloneable: var tree = { "left" : { "left" : null, "right" : null, "data" : 3 }, "right" : null, "data" : 8 }; // This would kind-of work, but you would get 2 copies of the // inner node instead of 2 references to the same copy var directedAcylicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; directedAcyclicGraph["right"] = directedAcyclicGraph["left"]; // Cloning this would cause a stack overflow due to infinite recursion: var cyclicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; cyclicGraph["right"] = cyclicGraph;
它将无法处理任何JavaScript对象,但它可能足以用于许多目的,只要您不认为它只适用于您抛出的任何内容.
如果您不在对象中使用函数,则可以使用以下非常简单的衬垫:
const a = {
string: 'string',
number: 123,
bool: false,
nul: null,
date: new Date(), // stringified
undef: undefined, // lost
inf: Infinity, // forced to 'null'
}
console.log(a);
console.log(typeof a.date); // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date); // result of .toISOString()
这适用于包含对象,数组,字符串,布尔值和数字的所有类型的对象.
另请参阅本文关于在向工作人员发送消息和从工作人员发布消息时使用的浏览器的结构化克隆算法.它还包含深度克隆功能.
使用jQuery,您可以使用extend进行浅层复制:
var copiedObject = jQuery.extend({}, originalObject)
对copiedObject的后续更改不会影响originalObject,反之亦然.
或者进行深层复制:
var copiedObject = jQuery.extend(true, {}, originalObject)
在ECMAScript 6中有Object.assign方法,它将所有可枚举的自有属性的值从一个对象复制到另一个对象.例如:
var x = {myProp: "value"}; var y = Object.assign({}, x);
但请注意,嵌套对象仍会被复制为引用.
每个MDN:
如果你想要浅拷贝,请使用 Object.assign({}, a)
对于"深度"复制,请使用 JSON.parse(JSON.stringify(a))
不需要外部库,但您需要首先检查浏览器兼容性.
有很多答案,但没有提到ECMAScript 5 中的Object.create,它确实没有给你一个精确的副本,但是将源设置为新对象的原型.
因此,这不是问题的确切答案,但它是一个单行解决方案,因而优雅.它适用于2种情况:
这种继承是有用的(呃!)
源对象不会被修改的地方,从而使两个对象之间的关系成为非问题.
例:
var foo = { a : 1 }; var bar = Object.create(foo); foo.a; // 1 bar.a; // 1 foo.a = 2; bar.a; // 2 - prototype changed bar.a = 3; foo.a; // Still 2, since setting bar.a makes it an "own" property
为什么我认为这个解决方案更优越?它是原生的,因此没有循环,没有递归.但是,旧版浏览器需要填充.
一种Object.assign
方法是ECMAScript 2015(ES6)标准的一部分,完全符合您的需求.
var clone = Object.assign({}, obj);
Object.assign()方法用于将所有可枚举自身属性的值从一个或多个源对象复制到目标对象.
阅读更多...
该填充工具以支持旧的浏览器:
if (!Object.assign) { Object.defineProperty(Object, 'assign', { enumerable: false, configurable: true, writable: true, value: function(target) { 'use strict'; if (target === undefined || target === null) { throw new TypeError('Cannot convert first argument to object'); } var to = Object(target); for (var i = 1; i < arguments.length; i++) { var nextSource = arguments[i]; if (nextSource === undefined || nextSource === null) { continue; } nextSource = Object(nextSource); var keysArray = Object.keys(nextSource); for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { var nextKey = keysArray[nextIndex]; var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); if (desc !== undefined && desc.enumerable) { to[nextKey] = nextSource[nextKey]; } } } return to; } }); }
互联网上的大多数解决方案存在几个问题.所以我决定进行跟进,其中包括为什么接受的答案不应被接受.
我想深入复制一个Javascript Object
与它的所有孩子和他们的孩子,等等.但因为我不是那种正常的开发者,我Object
有正常的 properties
,circular structures
甚至是nested objects
.
所以让我们创建一个circular structure
和nested object
第一个.
function Circ() { this.me = this; } function Nested(y) { this.y = y; }
让我们把所有东西放在一起Object
命名a
.
var a = { x: 'a', circ: new Circ(), nested: new Nested('a') };
接下来,我们要复制a
到一个名为变量的变量中b
.
var b = a; b.x = 'b'; b.nested.y = 'b';
你知道这里发生了什么,因为如果不是你甚至不会落在这个伟大的问题上.
console.log(a, b); a --> Object { x: "b", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } } b --> Object { x: "b", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } }
现在让我们找到一个解决方案.
我尝试的第一次尝试是使用JSON
.
var b = JSON.parse( JSON.stringify( a ) ); b.x = 'b'; b.nested.y = 'b';
不要浪费太多时间,你会得到TypeError: Converting circular structure to JSON
.
让我们来看看接受的答案.
function cloneSO(obj) { // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { var copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = cloneSO(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { var copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }
看起来不错,嘿?它是对象的递归副本,也可以处理其他类型Date
,但这不是必需的.
var b = cloneSO(a); b.x = 'b'; b.nested.y = 'b';
递归并circular structures
不能很好地一起工作......RangeError: Maximum call stack size exceeded
在和我的同事争吵之后,我的老板问我们发生了什么,他在谷歌搜索后发现了一个简单的解决方案.它被称为Object.create
.
var b = Object.create(a); b.x = 'b'; b.nested.y = 'b';
这个解决方案前一段时间被添加到Javascript甚至处理circular structure
.
console.log(a, b); a --> Object { x: "a", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } } b --> Object { x: "b", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } }
...而且你看,它不适用于里面的嵌套结构.
Object.create
就像IE 8一样,在旧版浏览器中有一个polyfill .它类似于Mozilla推荐的东西,当然,它并不完美,导致与原生解决方案相同的问题.
function F() {}; function clonePF(o) { F.prototype = o; return new F(); } var b = clonePF(a); b.x = 'b'; b.nested.y = 'b';
我已经F
超出范围,所以我们可以看看instanceof
告诉我们的是什么.
console.log(a, b); a --> Object { x: "a", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } } b --> F { x: "b", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } } console.log(typeof a, typeof b); a --> object b --> object console.log(a instanceof Object, b instanceof Object); a --> true b --> true console.log(a instanceof F, b instanceof F); a --> false b --> true
与本机解决方案相同的问题,但输出稍差.
在挖掘时,我发现了一个类似的问题(在Javascript中,当执行深层复制时,如何避免循环,由于属性是"这个"?)到这个,但有一个更好的解决方案.
function cloneDR(o) { const gdcc = "__getDeepCircularCopy__"; if (o !== Object(o)) { return o; // primitive value } var set = gdcc in o, cache = o[gdcc], result; if (set && typeof cache == "function") { return cache(); } // else o[gdcc] = function() { return result; }; // overwrite if (o instanceof Array) { result = []; for (var i=0; i让我们来看看输出......
console.log(a, b); a --> Object { x: "a", circ: Object { me: Object { ... } }, nested: Object { y: "a" } } b --> Object { x: "b", circ: Object { me: Object { ... } }, nested: Object { y: "b" } } console.log(typeof a, typeof b); a --> object b --> object console.log(a instanceof Object, b instanceof Object); a --> true b --> true console.log(a instanceof F, b instanceof F); a --> false b --> false要求相匹配,但仍有一些小问题,包括不断变化
instance
的nested
和circ
给Object
.共享叶子的树的结构将不会被复制,它们将成为两个独立的叶子:
[Object] [Object] / \ / \ / \ / \ |/_ _\| |/_ _\| [Object] [Object] ===> [Object] [Object] \ / | | \ / | | _\| |/_ \|/ \|/ [Object] [Object] [Object]结论
使用递归和缓存的最后一个解决方案可能不是最好的,但它是对象的真正深层副本.它操作简单
properties
,circular structures
和nested object
,但它会弄乱他们的实例,同时克隆.的jsfiddle
所以这个问题就是避免这个问题:)
对上面提供的解决方案进行了正确的分析,但作者得出的结论表明这个问题没有解决方案.
令人遗憾的是JS不包含本机克隆功能.
最好的答案在这里!恭喜您,先生!
9> dule..:如果你对浅拷贝没问题,那么underscore.js库有一个克隆方法.
y = _.clone(x);或者你可以扩展它
copiedObject = _.extend({},originalObject);
谢谢.在Meteor服务器上使用此技术.
10> Alireza..:好吧,想象一下你有这个对象,你想要克隆它:
let obj = {a:1, b:2, c:3}; //ES6要么
var obj = {a:1, b:2, c:3}; //ES5答案主要取决于你使用哪个ECMAscript
ES6+
,你可以简单地Object.assign
用来做克隆:let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};或使用像这样的传播运算符:
let cloned = {...obj}; //new {a:1, b:2, c:3};但是如果你使用
ES5
,你可以使用很少的方法,但是JSON.stringify
,确保你不要使用大量的数据来复制,但在许多情况下它可能是一种方便的方式,如下所示:let cloned = JSON.parse(JSON.stringify(obj)); //new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over
`Object.assign`进行浅拷贝(就像传播,@ Alizera)
11> Kris Walker..:一个特别不优雅的解决方案是使用JSON编码来制作没有成员方法的对象的深层副本.方法是对您的目标对象进行JSON编码,然后通过对其进行解码,您将获得所需的副本.您可以根据需要尽可能多地进行解码.
当然,函数不属于JSON,因此这仅适用于没有成员方法的对象.
这种方法非常适合我的用例,因为我将JSON blob存储在键值存储中,当它们作为JavaScript API中的对象公开时,每个对象实际上都包含对象原始状态的副本,所以我们可以在调用者突变暴露的对象后计算增量.
var object1 = {key:"value"}; var object2 = object1; object2 = JSON.stringify(object1); object2 = JSON.parse(object2); object2.key = "a change"; console.log(object1);// returns value
函数不是JSON规范的一部分,因为它们不是传输数据的安全(或智能)方式,这就是JSON的用途.我知道Firefox中的原生JSON编码器只是忽略传递给它的函数,但我不确定其他人的行为.
12> musemind..:您可以简单地使用spread属性来复制没有引用的对象.但要小心(请参阅注释),'copy'只是在最低的对象/数组级别.嵌套属性仍然是引用!
完整克隆:
let x = {a: 'value1'} let x2 = {...x} // => mutate without references: x2.a = 'value2' console.log(x.a) // => 'value1'克隆二级引用:
const y = {a: {b: 'value3'}} const y2 = {...y} // => nested object is still a references: y2.a.b = 'value4' console.log(y.a.b) // => 'value4'
JavaScript本身实际上不支持深度克隆.使用实用功能.例如Ramda:
http://ramdajs.com/docs/#clone
这是有效的,但请记住这是一个SHALLOW副本,因此对其他对象的任何深度引用仍然是引用!
13> 小智..:对于那些使用AngularJS的人来说,还有直接的方法来克隆或扩展这个库中的对象.
var destination = angular.copy(source);要么
angular.copy(source, destination);更多angular.copy 文档 ......
这是FYI的深层副本。
14> Jan Turoň..:A.Levy的答案几乎完成,这是我的小贡献:有一种方法如何处理递归引用,看到这一行
if(this[attr]==this) copy[attr] = copy;
如果对象是XML DOM元素,我们必须使用cloneNode代替
if(this.cloneNode) return this.cloneNode(true);
受A.Levy详尽的研究和Calvin的原型设计方法的启发,我提供了这个解决方案:
Object.prototype.clone = function() { if(this.cloneNode) return this.cloneNode(true); var copy = this instanceof Array ? [] : {}; for(var attr in this) { if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone) copy[attr] = this[attr]; else if(this[attr]==this) copy[attr] = copy; else copy[attr] = this[attr].clone(); } return copy; } Date.prototype.clone = function() { var copy = new Date(); copy.setTime(this.getTime()); return copy; } Number.prototype.clone = Boolean.prototype.clone = String.prototype.clone = function() { return this; }另见Andy Burke在答案中的注释.
`Date.prototype.clone = function(){return new Date(+ this)};`
15> Calvin..:从这篇文章:如何在Javascript中复制数组和对象 Brian Huisman:
Object.prototype.clone = function() { var newObj = (this instanceof Array) ? [] : {}; for (var i in this) { if (i == 'clone') continue; if (this[i] && typeof this[i] == "object") { newObj[i] = this[i].clone(); } else newObj[i] = this[i] } return newObj; };
这很接近,但不适用于任何对象.尝试使用此方法克隆Date对象.并非所有属性都是可枚举的,因此它们不会全部显示在for/in循环中.
@ iPadDeveloper2011上面的代码中有一个错误,它创建了一个名为"i"的全局变量(对于i),而不是"(对于此中的var i)".我有足够的业力来编辑和修复它,所以我做了.
为什么不是`var copiedObj = Object.create(obj);`这也是一个好方法?
16> picardo..:这是您可以使用的功能.
function clone(obj) { if(obj == null || typeof(obj) != 'object') return obj; var temp = new obj.constructor(); for(var key in obj) temp[key] = clone(obj[key]); return temp; }
这个答案非常接近,但不太正确.如果您尝试克隆Date对象,则不会获得相同的日期,因为对Date构造函数的调用使用当前日期/时间初始化新Date.该值不可枚举,也不会被for/in循环复制.
17> João Oliveir..:在ES-6中,您可以简单地使用Object.assign(...).例如:
let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj);这里有一个很好的参考:https: //googlechrome.github.io/samples/object-assign-es6/
它没有深度克隆对象.
18> Pavan Garre..:在ECMAScript 2018中
let objClone = { ...obj };请注意,嵌套对象仍会被复制为引用.
19> Rob Evans..:您可以使用一行代码克隆对象并从前一个引用中删除任何引用.简单地说:
var obj1 = { text: 'moo1' }; var obj2 = Object.create(obj1); // Creates a new clone without references obj2.text = 'moo2'; // Only updates obj2's text property console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}对于当前不支持Object.create的浏览器/引擎,您可以使用此polyfill:
// Polyfill Object.create if it does not exist if (!Object.create) { Object.create = function (o) { var F = function () {}; F.prototype = o; return new F(); }; }
这是用obj1创建obj2的原型.它只能用于遮蔽obj2中的`text`成员.您没有制作副本,只是在obj2上找不到成员时推迟到原型链.
这不会“没有引用”创建它,只是将引用移至原型。仍然是参考。如果原始属性发生变化,则“克隆”中的原型属性也会发生变化。根本不是克隆。
20> 小智..:对克隆简单对象感兴趣:
JSON.parse(JSON.stringify(json_original));
来源:如何通过引用将JavaScript对象复制到新变量?
21> Charles Merr..:对旧问题的新答案!如果您有幸使用带有Spread语法的 ECMAScript 2016(ES6),那很容易.
keepMeTheSame = {first: "Me!", second: "You!"}; cloned = {...keepMeTheSame}这为对象的浅拷贝提供了一种干净的方法.进行深层复制,意味着在每个递归嵌套对象中设置每个值的新副本,需要上面较重的解决方案.
JavaScript不断发展.
当您在对象上定义了函数时,它不起作用
22> VaZaA..:使用Lodash:
var y = _.clone(x, true);
我更喜欢`_.cloneDeep(x)`,因为它基本上和上面一样,但读得更好.
OMG重新克隆克隆将是疯狂的.这是唯一合理的答案.
23> flori..:let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)ES6解决方案,如果你想(浅)克隆一个类实例而不只是一个属性对象.
24> ConductedCle..:我认为有一个简单而有效的答案.在深度复制中有两个问题:
保持属性彼此依赖.
并使方法在克隆对象上保持活跃.
所以我认为一个简单的解决方案是首先序列化和反序列化,然后对其进行分配以复制函数.
let deepCloned = JSON.parse(JSON.stringify(source)); let merged = Object.assign({}, source); Object.assign(merged, deepCloned);虽然这个问题有很多答案,但我希望这个问题也有帮助.
我正在使用JSON.parse(JSON.stringify(source))。一直在工作。
@Misha,这样您将错过所有功能。术语“作品”具有许多含义。
25> 小智..:对于深层复制和克隆,先JSON.stringify然后再JSON.parse对象:
obj = { a: 0 , b: { c: 0}}; let deepClone = JSON.parse(JSON.stringify(obj)); obj.a = 5; obj.b.c = 5; console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}
26> heinob..:我只想添加到
Object.create
这篇文章中的所有解决方案,这对于nodejs来说并不是以所需的方式工作.在Firefox中的结果
var a = {"test":"test"}; var b = Object.create(a); console.log(b);´是
{test:"test"}
.在nodejs中
{}
27> Radu Simione..:这是A. Levy的代码,同时还处理功能和多/循环引用克隆适应 - 这是什么意思是,如果在树中这两种性能克隆是同一个对象的引用,克隆的对象树将这些属性指向引用对象的同一个克隆.这也解决了循环依赖的情况,如果不处理,会导致无限循环.算法的复杂性是O(n)
function clone(obj){ var clonedObjectsArray = []; var originalObjectsArray = []; //used to remove the unique ids when finished var next_objid = 0; function objectId(obj) { if (obj == null) return null; if (obj.__obj_id == undefined){ obj.__obj_id = next_objid++; originalObjectsArray[obj.__obj_id] = obj; } return obj.__obj_id; } function cloneRecursive(obj) { if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj; // Handle Date if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { var copy = []; for (var i = 0; i < obj.length; ++i) { copy[i] = cloneRecursive(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { if (clonedObjectsArray[objectId(obj)] != undefined) return clonedObjectsArray[objectId(obj)]; var copy; if (obj instanceof Function)//Handle Function copy = function(){return obj.apply(this, arguments);}; else copy = {}; clonedObjectsArray[objectId(obj)] = copy; for (var attr in obj) if (attr != "__obj_id" && obj.hasOwnProperty(attr)) copy[attr] = cloneRecursive(obj[attr]); return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); } var cloneObj = cloneRecursive(obj); //remove the unique ids for (var i = 0; i < originalObjectsArray.length; i++) { delete originalObjectsArray[i].__obj_id; }; return cloneObj; }一些快速测试
var auxobj = { prop1 : "prop1 aux val", prop2 : ["prop2 item1", "prop2 item2"] }; var obj = new Object(); obj.prop1 = "prop1_value"; obj.prop2 = [auxobj, auxobj, "some extra val", undefined]; obj.nr = 3465; obj.bool = true; obj.f1 = function (){ this.prop1 = "prop1 val changed by f1"; }; objclone = clone(obj); //some tests i've made console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool)); objclone.f1(); console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1')); objclone.f1.prop = 'some prop'; console.log("test function cloning 2: " + (obj.f1.prop == undefined)); objclone.prop2[0].prop1 = "prop1 aux val NEW"; console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1)); console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
28> 小智..:function clone(src, deep) { var toString = Object.prototype.toString; if(!src && typeof src != "object"){ //any non-object ( Boolean, String, Number ), null, undefined, NaN return src; } //Honor native/custom clone methods if(src.clone && toString.call(src.clone) == "[object Function]"){ return src.clone(deep); } //DOM Elements if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){ return src.cloneNode(deep); } //Date if(toString.call(src) == "[object Date]"){ return new Date(src.getTime()); } //RegExp if(toString.call(src) == "[object RegExp]"){ return new RegExp(src); } //Function if(toString.call(src) == "[object Function]"){ //Wrap in another method to make sure == is not true; //Note: Huge performance issue due to closures, comment this :) return (function(){ src.apply(this, arguments); }); } var ret, index; //Array if(toString.call(src) == "[object Array]"){ //[].slice(0) would soft clone ret = src.slice(); if(deep){ index = ret.length; while(index--){ ret[index] = clone(ret[index], true); } } } //Object else { ret = src.constructor ? new src.constructor() : {}; for (var prop in src) { ret[prop] = deep ? clone(src[prop], true) : src[prop]; } } return ret; };
`if(!src && typeof src!="object"){`.我认为应该是`||`而不是`&&`.
29> Bert Regelin..:由于mindeavor声明要克隆的对象是"文字构造的"对象,因此解决方案可能是简单地多次生成对象而不是克隆对象的实例:
function createMyObject() { var myObject = { ... }; return myObject; } var myObjectInstance1 = createMyObject(); var myObjectInstance2 = createMyObject();
30> 小智..:我写了自己的实现.不确定它是否算作更好的解决方案:
/* a function for deep cloning objects that contains other nested objects and circular structures. objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object. index (z) | | | | | | depth (x) |_ _ _ _ _ _ _ _ _ _ _ _ /_/_/_/_/_/_/_/_/_/ /_/_/_/_/_/_/_/_/_/ /_/_/_/_/_/_/...../ /................./ /..... / / / /------------------ object length (y) / */以下是实施:
function deepClone(obj) { var depth = -1; var arr = []; return clone(obj, arr, depth); } /** * * @param obj source object * @param arr 3D array to store the references to objects * @param depth depth of the current object relative to the passed 'obj' * @returns {*} */ function clone(obj, arr, depth){ if (typeof obj !== "object") { return obj; } var length = Object.keys(obj).length; // native method to get the number of properties in 'obj' var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object if(result instanceof Array){ result.length = length; } depth++; // depth is increased because we entered an object here arr[depth] = []; // this is the x-axis, each index here is the depth arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props) // start the depth at current and go down, cyclic structures won't form on depths more than the current one for(var x = depth; x >= 0; x--){ // loop only if the array at this depth and length already have elements if(arr[x][length]){ for(var index = 0; index < arr[x][length].length; index++){ if(obj === arr[x][length][index]){ return obj; } } } } arr[depth][length].push(obj); // store the object in the array at the current depth and length for (var prop in obj) { if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth); } return result; }
31> 小智..:JanTotoň的答案非常接近,由于兼容性问题,可能是最好在浏览器中使用,但它可能会导致一些奇怪的枚举问题.例如,执行:
for ( var i in someArray ) { ... }迭代遍历数组的元素后,将clone()方法赋给i.这是一个避免枚举并适用于node.js的改编:
Object.defineProperty( Object.prototype, "clone", { value: function() { if ( this.cloneNode ) { return this.cloneNode( true ); } var copy = this instanceof Array ? [] : {}; for( var attr in this ) { if ( typeof this[ attr ] == "function" || this[ attr ] == null || !this[ attr ].clone ) { copy[ attr ] = this[ attr ]; } else if ( this[ attr ] == this ) { copy[ attr ] = copy; } else { copy[ attr ] = this[ attr ].clone(); } } return copy; } }); Object.defineProperty( Date.prototype, "clone", { value: function() { var copy = new Date(); copy.setTime( this.getTime() ); return copy; } }); Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } ); Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } ); Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );这避免了使clone()方法可枚举,因为defineProperty()默认为可枚举为false.
32> ooo..:(以下是主要@的整合马切伊·布考斯基,@ A.征,@ 扬TURO?,@ 热度的答案,@ LeviRoberts,@ RobG的意见,非常感谢他们!)
深复制?—是的!(大多);
浅拷贝?—不!(除外Proxy
)。我真诚欢迎大家参加测试
功能clone()
。
另外,defineProp()
旨在轻松,快速地(重新)定义或复制任何类型的描述符。"use strict" function clone(object) { /* Deep copy objects by value rather than by reference, exception: `Proxy` */ const seen = new WeakMap() return clone(object) function clone(object) { if (object !== Object(object)) return object /* —— Check if the object belongs to a primitive data type */ if (object instanceof Node) return object.cloneNode(true) /* —— Clone DOM trees */ let _object // The clone of object switch (object.constructor) { case Array: case Object: _object = cloneObject(object) break case Date: _object = new Date(+object) break case Function: const fnStr = String(object) _object = new Function("return " + (/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr) ? "function " : "" ) + fnStr )() copyPropDescs(_object, object) break case RegExp: _object = new RegExp(object) break default: switch (Object.prototype.toString.call(object.constructor)) { // // Stem from: case "[object Function]": // `class` case "[object Undefined]": // `Object.create(null)` _object = cloneObject(object) break default: // `Proxy` _object = object } } return _object } function cloneObject(object) { if (seen.has(object)) return seen.get(object) /* —— Handle recursive references (circular structures) */ const _object = Array.isArray(object) ? [] : Object.create(Object.getPrototypeOf(object)) /* —— Assign [[Prototype]] for inheritance */ seen.set(object, _object) /* —— Make `_object` the associative mirror of `object` */ Reflect.ownKeys(object).forEach(key => defineProp(_object, key, { value: clone(object[key]) }, object) ) return _object } function copyPropDescs(target, source) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source) ) } } function defineProp(object, key, descriptor = {}, copyFrom = {}) { const { configurable: _configurable, writable: _writable } = Object.getOwnPropertyDescriptor(object, key) || { configurable: true, writable: true } const test = _configurable // Can redefine property && (_writable === undefined || _writable) // Can assign to property if (!test || arguments.length <= 2) return test const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key) || { configurable: true, writable: true } // Custom… || {}; // …or left to native default settings ["get", "set", "value", "writable", "enumerable", "configurable"] .forEach(attr => descriptor[attr] === undefined && (descriptor[attr] = basisDesc[attr]) ) const { get, set, value, writable, enumerable, configurable } = descriptor return Object.defineProperty(object, key, { enumerable, configurable, ...get || set ? { get, set } // Accessor descriptor : { value, writable } // Data descriptor }) }//测试
"use strict" const obj0 = { u: undefined, nul: null, t: true, num: 9, str: "", sym: Symbol("symbol"), [Symbol("e")]: Math.E, arr: [[0], [1, 2]], d: new Date(), re: /f/g, get g() { return 0 }, o: { n: 0, o: { f: function (...args) { } } }, f: { getAccessorStr(object) { return [] .concat(... Object.values(Object.getOwnPropertyDescriptors(object)) .filter(desc => desc.writable === undefined) .map(desc => Object.values(desc)) ) .filter(prop => typeof prop === "function") .map(String) }, f0: function f0() { }, f1: function () { }, f2: 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 = "nested array" let obj1 = clone(obj0) obj1.o.n = 1 obj1.o.o.g = function g(a = 0, b = 0) { return a + b } obj1.arr[1][1] = 3 obj1.d.setTime(+obj0.d + 60 * 1000) obj1.arr.tint.is = "enumerable? no" obj1.arr[0].name = "a nested arr" defineProp(obj1, "s", { set(v) { this._s = v + 1 } }) defineProp(obj1.re, "multiline", { value: true }) console.log("\n\n" + "-".repeat(2 ** 6)) console.log(">:>: Test - Routinely") console.log("obj0:\n ", JSON.stringify(obj0)) console.log("obj1:\n ", JSON.stringify(obj1)) console.log() console.log("obj0:\n ", obj0) console.log("obj1:\n ", obj1) console.log() console.log("obj0\n ", ".arr.tint:", obj0.arr.tint, "\n ", ".arr[0].name:", obj0.arr[0].name ) console.log("obj1\n ", ".arr.tint:", obj1.arr.tint, "\n ", ".arr[0].name:", obj1.arr[0].name ) console.log() console.log("Accessor-type descriptor\n ", "of obj0:", obj0.f.getAccessorStr(obj0), "\n ", "of obj1:", obj1.f.getAccessorStr(obj1), "\n ", "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ", " ? (obj0 , obj1) ._s:", obj0._s, ",", obj1._s ) console.log("—— obj0 has not been interfered.") console.log("\n\n" + "-".repeat(2 ** 6)) console.log(">:>: Test - Circular structures") obj0.o.r = {} obj0.o.r.recursion = obj0.o obj0.arr[1] = obj0.arr obj1 = clone(obj0) console.log("obj0:\n ", obj0) console.log("obj1:\n ", obj1) console.log("Clear obj0's recursion:", obj0.o.r.recursion = null, obj0.arr[1] = 1 ) console.log( "obj0\n ", ".o.r:", obj0.o.r, "\n ", ".arr:", obj0.arr ) console.log( "obj1\n ", ".o.r:", obj1.o.r, "\n ", ".arr:", obj1.arr ) console.log("—— obj1 has not been interfered.") console.log("\n\n" + "-".repeat(2 ** 6)) console.log(">:>: Test - Classes") class Person { constructor(name) { this.name = name } } class Boy extends Person { } Boy.prototype.sex = "M" const boy0 = new Boy boy0.hobby = { sport: "spaceflight" } const boy1 = clone(boy0) boy1.hobby.sport = "superluminal flight" boy0.name = "one" boy1.name = "neo" console.log("boy0:\n ", boy0) console.log("boy1:\n ", boy1) console.log("boy1's prototype === boy0's:", Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0) )参考文献语言技巧
Object.create()
| MDN
Object.defineProperties()
| MDN属性的可枚举性和所有权| MDN
TypeError:循环对象值| MDN
有条件地向对象添加道具
33> Ashok R..:使用lodash _.cloneDeep()。
浅表复制:lodash _.clone()
可以通过简单地复制参考来进行浅表复制。
let obj1 = { a: 0, b: { c: 0, e: { f: 0 } } }; let obj3 = _.clone(obj1); obj1.a = 4; obj1.b.c = 4; obj1.b.e.f = 100; console.log(JSON.stringify(obj1)); //{"a":4,"b":{"c":4,"e":{"f":100}}} console.log(JSON.stringify(obj3)); //{"a":0,"b":{"c":4,"e":{"f":100}}}
深度复制:lodash _.cloneDeep()
字段被取消引用:而不是引用要复制的对象
let obj1 = { a: 0, b: { c: 0, e: { f: 0 } } }; let obj3 = _.cloneDeep(obj1); obj1.a = 100; obj1.b.c = 100; obj1.b.e.f = 100; console.log(JSON.stringify(obj1)); {"a":100,"b":{"c":100,"e":{"f":100}}} console.log(JSON.stringify(obj3)); {"a":0,"b":{"c":0,"e":{"f":0}}}