var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
唐't waste too much time on it, you' ll得到 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
/*
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;
}
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
除了不可枚举的属性,当您尝试复制具有隐藏属性的对象时,您将遇到更严峻的问题 . 例如, prototype 是函数的隐藏属性 . 此外,对象的原型使用属性 __proto__ 引用,该属性也是隐藏的,并且不会被遍历源对象属性的for / in循环复制 . 我认为 __proto__ 可能特定于Firefox 's JavaScript interpreter and it may be something different in other browsers, but you get the picture. Not everything is enumerable. You can copy a hidden attribute if you know its name, but I don'知道以任何方式自动发现它 .
寻求优雅解决方案的另一个障碍是正确设置原型继承的问题 . 如果你的源对象的原型是 Object ,那么就这么简单使用 {} 创建一个新的通用对象会起作用,但是如果源的原型是 Object 的某个后代,那么你将会错过使用 hasOwnProperty 过滤器跳过的原型中的其他成员,或原型中的那些,但是没有't enumerable in the first place. One solution might be to call the source object' s 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 的日期字符串将落后 d2 的5秒 . 使一个 Date 与另一个相同的方法是调用 setTime 方法,但这是特定于 Date 类的 . 我不认为这个问题有一个防弹的一般解决方案,但我会很高兴出错!
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.");
}
// 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;
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();
};
}
function createMyObject()
{
var myObject =
{
...
};
return myObject;
}
var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
30 回答
ES6解决方案,如果你想(浅)克隆一个 class instance 而不仅仅是一个属性对象 .
互联网上的大多数解决方案存在几个问题 . 所以我决定进行跟进,其中包括为什么接受的答案不应被接受 .
出发情况
我想要 deep-copy 一个Javascript
Object
与它的所有孩子和他们的孩子等等 . 但由于我不是一个普通的开发人员,我的Object
正常properties
,circular structures
甚至nested objects
.所以让我们首先创建一个
circular structure
和一个nested object
.让我们把所有东西放在一个名为
a
的_210662中 .接下来,我们要将
a
复制到名为b
的变量中并对其进行变异 .你知道在这里发生了什么,因为如果不是你甚至不会落在这个伟大的问题上 .
现在让我们找到一个解决方案 .
JSON
我尝试的第一次尝试是使用
JSON
.唐't waste too much time on it, you' ll得到
TypeError: Converting circular structure to JSON
.递归副本(接受的“答案”)
让我们来看看接受的答案 .
看起来不错,嘿?它是对象的递归副本,也处理其他类型,如
Date
,但这不是必需的 .递归和
circular structures
不能很好地协同工作......RangeError: Maximum call stack size exceeded
原生解决方案
在和我的同事争吵之后,我的老板问我们发生了什么,他在谷歌搜索后发现了一个简单的解决方案 . 它被称为
Object.create
.这个解决方案在前一段时间被添加到Javascript中,甚至可以处理
circular structure
....而且你看,它不适用于里面的嵌套结构 .
本地解决方案的polyfill
在旧浏览器中有一个
Object.create
的polyfill就像IE 8一样 . 它's something like recommended by Mozilla, and of course, it'不完美,导致与原生解决方案相同的问题 .我把
F
放在了范围之外,所以我们可以看一下instanceof
告诉我们的内容 .与本机解决方案相同的问题,但输出稍差 .
更好(但不完美)的解决方案
在挖掘时,我发现了一个类似的问题(In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being "this"?)到这个,但有一个更好的解决方案 .
让我们来看看输出......
需求是匹配的,但仍然存在一些较小的问题,包括将
nested
的instance
和circ
更改为Object
.结论
使用递归和缓存的最后一个解决方案可能不是最好的,但它是对象的深度副本 . 它处理简单的
properties
,circular structures
和nested object
,但它会在克隆时弄乱它们的实例 .jsfiddle
在ECMAScript 6中有Object.assign方法,它将所有可枚举的自有属性的值从一个对象复制到另一个对象 . 例如:
但请注意,嵌套对象仍会被复制为引用 .
使用jQuery,您可以使用extend shallow copy :
对copiedObject的后续更改不会影响originalObject,反之亦然 .
或制作 deep copy :
有关W3C的"Safe passing of structured data"算法,请参阅http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data,该算法旨在由浏览器实现,以便将数据传递给Web worker . 但是,它有一些限制,因为它不处理功能 . 有关更多信息,请参阅https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm,包括JS中的替代算法,它可以帮助您了解其中的一部分 .
对旧问题的新答案!如果您有幸使用ECMAScript 2016(ES6)和Spread Syntax,那很容易 .
这为对象的浅拷贝提供了一种干净的方法 . 制作深层复制,意味着在每个递归嵌套对象中设置每个值的新副本,需要上面较重的解决方案 .
JavaScript不断发展 .
我写了自己的实现 . 不确定它是否算作更好的解决方案:
以下是实施:
对于那些使用AngularJS的人来说,还有直接的方法来克隆或扩展这个库中的对象 .
要么
更多angular.copy documentation ...
使用Lodash:
如果你对浅拷贝没问题,那么underscore.js库有一个clone方法 .
或者你可以扩展它
在一行代码中克隆Javascript对象的优雅方法
Object.assign
方法是ECMAScript 2015(ES6)标准的一部分,完全符合您的要求需要 .Read more...
polyfill 支持旧浏览器:
按MDN:
如果你想要浅拷贝,请使用
Object.assign({}, a)
对于"deep"副本,请使用
JSON.parse(JSON.stringify(a))
不需要外部库,但需要检查browser compatibility first .
有很多答案,但没有提到ECMAScript 5中的Object.create,它确实没有给你一个精确的副本,但是将源设置为新对象的原型 .
因此,这不是问题的确切答案,但它是一个单行解决方案,因而优雅 . 它适用于2种情况:
这种继承有用(呃!)
不会修改源对象,从而使两个对象之间的关系成为非问题 .
例:
为什么我认为这个解决方案更优越?它是原生的,因此没有循环,没有递归 . 但是,旧版浏览器需要使用polyfill .
您可以简单地使用spread property来复制没有引用的对象 . 但是要小心(参见注释),'copy'只是在最低的对象/数组级别 . 嵌套属性仍然是引用!
Complete clone:
Clone with references on second level:
JavaScript本身实际上不支持深度克隆 . 使用实用程序功能 . 例如Ramda:
JanTotoň的答案非常接近,由于兼容性问题,可能是最好在浏览器中使用,但它可能会导致一些奇怪的枚举问题 . 例如,执行:
迭代遍历数组的元素后,将clone()方法赋给i . 这是一个避免枚举并适用于node.js的改编:
这避免了使clone()方法可枚举,因为defineProperty()默认为可枚举为false .
这是您可以使用的功能 .
如果您不在对象中使用函数,则可以使用以下非常简单的内容:
这适用于包含对象,数组,字符串,布尔值和数字的所有类型的对象 .
另请参阅this article about the structured clone algorithm of browsers,这是在向工作人员发送消息和从工作人员发布消息时使用的 . 它还包含深度克隆功能 .
来自这篇文章:Brian Huisman的How to copy arrays and objects in Javascript:
在ECMAScript 2018中
请注意 nested objects 仍然被复制 as a reference.
一个特别不优雅的解决方案是使用JSON编码来制作没有成员方法的对象的深层副本 . 方法是对您的目标对象进行JSON编码,然后通过对其进行解码,您将获得所需的副本 . 您可以根据需要进行多次解码,然后根据需要进行复制 .
当然,函数不属于JSON,因此这仅适用于没有成员方法的对象 .
这种方法非常适合我的用例,因为我将JSON blob存储在键值存储中,当它们作为JavaScript API中的对象公开时,每个对象实际上都包含对象原始状态的副本,所以我们可以在调用者突变暴露的对象后计算增量 .
A.Levy的答案几乎完成,这是我的小贡献: there is a way how to handle recursive references ,看到这一行
if(this[attr]==this) copy[attr] = copy;
如果对象是XML DOM元素,我们必须使用 cloneNode
if(this.cloneNode) return this.cloneNode(true);
受A.Levy详尽的研究和Calvin的原型设计方法的启发,我提供了这个解决方案:
另见Andy Burke在答案中的注释 .
对克隆简单对象感兴趣:
JSON.parse(JSON.stringify(json_original));
资料来源:How to copy JavaScript object to new variable NOT by reference?
为JavaScript中的任何对象执行此操作并不简单或直接 . 您将遇到错误地从对象原型中拾取属性的问题,该属性应保留在原型中而不会复制到新实例 . 例如,如果要将
clone
方法添加到Object.prototype
,如某些答案所示,则需要显式跳过该属性 . 但是,如果在Object.prototype
或其他中间原型中添加了其他额外方法,那么您需要使用hasOwnProperty方法检测无法预料的非本地属性 .除了不可枚举的属性,当您尝试复制具有隐藏属性的对象时,您将遇到更严峻的问题 . 例如,
prototype
是函数的隐藏属性 . 此外,对象的原型使用属性__proto__
引用,该属性也是隐藏的,并且不会被遍历源对象属性的for / in循环复制 . 我认为__proto__
可能特定于Firefox 's JavaScript interpreter and it may be something different in other browsers, but you get the picture. Not everything is enumerable. You can copy a hidden attribute if you know its name, but I don'知道以任何方式自动发现它 .寻求优雅解决方案的另一个障碍是正确设置原型继承的问题 . 如果你的源对象的原型是
Object
,那么就这么简单使用{}
创建一个新的通用对象会起作用,但是如果源的原型是Object
的某个后代,那么你将会错过使用hasOwnProperty
过滤器跳过的原型中的其他成员,或原型中的那些,但是没有't enumerable in the first place. One solution might be to call the source object' sconstructor
属性来获取初始复制对象,然后复制属性,但是你仍然不会得到不可枚举的属性 . 例如,Date对象将其数据存储为隐藏成员:d1
的日期字符串将落后d2
的5秒 . 使一个Date
与另一个相同的方法是调用setTime
方法,但这是特定于Date
类的 . 我不认为这个问题有一个防弹的一般解决方案,但我会很高兴出错!当我不得不实施一般的深度复制时,我最终通过假设我只需复制一个普通的
Object
,Array
,Date
,String
,Number
或Boolean
来妥协 . 最后3种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变 . 我进一步假设Object
或Array
中包含的任何元素也将是该列表中的6种简单类型之一 . 这可以通过以下代码完成:只要对象和数组中的数据形成树结构,上述函数就可以适用于我提到的6种简单类型 . 也就是说,对象中的相同数据的引用不超过一个 . 例如:
它将无法处理任何JavaScript对象,但它可能足以用于许多目的,只要您不认为它只适用于您抛出的任何内容 .
在ES-6中,您可以简单地使用Object.assign(...) . 例如:
这里有一个很好的参考:https://googlechrome.github.io/samples/object-assign-es6/
您可以使用一行代码克隆对象并从前一个引用中删除任何引用 . 简单地说:
对于当前不支持Object.create的浏览器/引擎,您可以使用此polyfill:
这是A. Levy的代码的改编,也用于处理函数和多个/循环引用的克隆 - 这意味着如果克隆的树中的两个属性是同一对象的引用,则克隆的对象树将具有这些属性指向引用对象的同一个克隆 . 这也解决了循环依赖的情况,如果不处理,会导致无限循环 . 算法的复杂性是O(n)
一些快速测试
我只是想在这篇文章中添加所有的
Object.create
解决方案,这对于nodejs来说并不是以预期的方式工作 .在Firefox中的结果
是
{test:"test"}
.在nodejs中
由于mindeavor声明要克隆的对象是'literal-constructed'对象,因此解决方案可能是简单地多次生成对象而不是克隆对象的实例:
OK, 想象你下面有这个对象,你想克隆它:
要么
答案主要取决于你使用的 ECMAscript ,在
ES6+
中,你可以简单地使用Object.assign
来做克隆:或使用像这样的传播运算符:
但是如果你使用
ES5
,你可以使用很少的方法,但是JSON.stringify
,只是确保你没有使用大量的数据来复制,但在许多情况下它可能是一种方便的方式,如下所示: