我有一个对象 x
。我想将它复制为对象 y
,这样对 y
的更改就不会修改 x
。我意识到复制从内置 JavaScript 对象派生的对象会导致额外的、不需要的属性。这不是问题,因为我正在复制我自己的文字构造对象之一。
如何正确克隆 JavaScript 对象?
mObj=JSON.parse(JSON.stringify(jsonObject));
Object.create(o)
,它可以满足作者的所有要求?
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2;
执行此操作后,y.deep.key
也将为 2,因此 Object.create 不能用于克隆...
2022 年更新
有一个名为 structured cloning 的新 JS 标准。它适用于所有浏览器:
const clone = structuredClone(object);
旧答案
对 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
的日期字符串将比 d2
晚 5 秒。使一个 Date
与另一个相同的方法是调用 setTime
方法,但这是特定于 Date
类的。我认为这个问题没有万无一失的通用解决方案,尽管我很乐意犯错!
当我不得不实现一般的深度复制时,我最终妥协,假设我只需要复制一个普通的 Object
、Array
、Date
、String
、Number
或 Boolean
。最后 3 种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。我进一步假设 Object
或 Array
中包含的任何元素也将是该列表中的 6 个简单类型之一。这可以通过如下代码来完成:
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 对象,但它可能足以满足许多目的,只要你不认为它只适用于你扔给它的任何东西。
如果您不在对象中使用 Date
、函数、未定义、正则表达式或 Infinity,则一个非常简单的一行是 JSON.parse(JSON.stringify(object))
:
const a = { string: 'string', number: 123, bool: false, nul: null, date: new Date(), // 字符串化 undef: undefined, // 丢失 inf: Infinity, // 强制为 'null' } 控制台.log(a); console.log(typeof a.date); // 日期对象 const clone = JSON.parse(JSON.stringify(a));控制台.log(克隆); console.log(typeof clone.date); // .toISOString() 的结果
这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。
另请参阅在向工作人员发送消息或从工作人员发送消息时使用的 this article about the structured clone algorithm of browsers。它还包含深度克隆功能。
a={};b={}; a==b
是 false
。但是在 a=b
之后它变成了 true
,因为它不仅相同而且是同一个对象。
使用 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))
不需要外部库,但您需要检查 browser compatibility first。
有很多答案,但没有一个提到 ECMAScript 5 中的 Object.create,它诚然没有给你一个精确的副本,而是将源设置为新对象的原型。
因此,这不是问题的确切答案,但它是一种单线解决方案,因此很优雅。它最适合两种情况:
这种继承是有用的(呃!)源对象不会被修改的地方,因此这两个对象之间的关系不成问题。
例子:
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
为什么我认为这个解决方案更好?它是原生的,因此没有循环,没有递归。但是,较旧的浏览器将需要一个 polyfill。
一种在一行代码中克隆 Javascript 对象的优雅方法
Object.assign
方法是 ECMAScript 2015 (ES6) 标准的一部分,可以满足您的需要。
var clone = Object.assign({}, obj);
Object.assign() 方法用于将所有可枚举自身属性的值从一个或多个源对象复制到目标对象。
支持旧浏览器的 polyfill:
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;
}
});
}
objA = objB;
会导致各种头痛。这似乎已经解决了这个问题,至少现在......
互联网上的大多数解决方案都存在几个问题。所以我决定进行跟进,其中包括为什么不应该接受已接受的答案。
起始情况
我想深拷贝 Javascript Object
及其所有子级及其子级等。但由于我不是一个普通的开发者,我的 Object
有普通 properties
、circular structures
甚至 nested objects
。
因此,让我们先创建一个 circular structure
和一个 nested object
。
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
让我们将所有内容放在一个名为 a
的 Object
中。
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
我尝试的第一次尝试是使用 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"
}
}
...你看,它不适用于内部的嵌套结构。
原生解决方案的 polyfill
与 IE 8 一样,旧版浏览器中有一个用于 Object.create
的 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
与本机解决方案相同的问题,但输出稍差一些。
更好(但不完美)的解决方案
在四处挖掘时,我发现了一个与这个问题类似的问题 (In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being "this"?),但有一个更好的解决方案。
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<o.length; i++) {
result[i] = cloneDR(o[i]);
}
} else {
result = {};
for (var prop in o)
if (prop != gdcc)
result[prop] = cloneDR(o[prop]);
else if (set)
result[prop] = cloneDR(cache);
}
if (set) {
o[gdcc] = cache; // reset
} else {
delete o[gdcc]; // unset again
}
return result;
}
var b = cloneDR(a);
b.x = 'b';
b.nested.y = 'b';
让我们看看输出......
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
要求是匹配的,但还有一些小问题,包括将 nested
的 instance
和 circ
更改为 Object
。
共享叶子的树的结构不会被复制,它们将成为两个独立的叶子:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
结论
最后一个使用递归和缓存的解决方案可能不是最好的,但它是对象的真实深层副本。它处理简单的 properties
、circular structures
和 nested object
,但在克隆时会弄乱它们的实例。
如果您对浅拷贝没问题,underscore.js 库有一个 clone 方法。
y = _.clone(x);
或者你可以像这样扩展它
copiedObject = _.extend({},originalObject);
好的,假设你在下面有这个对象并且你想克隆它:
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
big chunk of data
的含义? 100KB? 100MB?谢谢!
Object.assign
制作浅拷贝(就像传播一样,@Alizera)
2020 年 7 月 6 日更新
有三 (3) 种方法可以在 JavaScript 中克隆对象。由于 JavaScript 中的对象是引用值,因此您不能简单地使用 = 进行复制。
方法是:
const food = { food: 'apple', drink: 'milk' }
// 1. Using the "Spread"
// ------------------
{ ...food }
// 2. Using "Object.assign"
// ------------------
Object.assign({}, food)
// 3. "JSON"
// ------------------
JSON.parse(JSON.stringify(food))
// RESULT:
// { food: 'apple', drink: 'milk' }
这可以作为参考总结。
JSON
方法将删除对象的任何方法
一种特别不优雅的解决方案是使用 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
{ 'foo': function() { return 1; } }
是一个文字构造的对象。
您可以简单地使用 spread property 来复制没有引用的对象。但要小心(见评论),“副本”只是在最低的对象/数组级别。嵌套属性仍然是引用!
完整克隆:
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 实际上本身并不支持深度克隆。使用效用函数。例如拉姆达:
http://ramdajs.com/docs/#clone
const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
const objClone = { ...obj };
请注意,嵌套对象仍被复制为引用。
const objDeepClone = JSON.parse(JSON.stringify(obj));
来自这篇文章:How to copy arrays and objects in Javascript by 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;
};
var copiedObj = Object.create(obj);
也不是一个好方法?
对于那些使用 AngularJS 的人,也有直接的方法来克隆或扩展这个库中的对象。
var destination = angular.copy(source);
或者
angular.copy(source, destination);
更多在 angular.copy documentation...
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;
}
new
正确调用构造函数。接受的答案没有。
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)};
表现
今天 2020.04.30 我在 MacOs High Sierra v10.13.6 上在 Chrome v81.0、Safari v13.1 和 Firefox v75.0 上执行所选解决方案的测试。
我专注于复制 DATA 的速度(具有简单类型字段的对象,而不是方法等)。解AI只能做浅拷贝,解JU可以做深拷贝。
浅拷贝的结果
解决方案 {...obj} (A) 在 chrome 和 firefox 上最快,在 safari 上中等速度
基于 Object.assign (B) 的解决方案在所有浏览器上都很快
jQuery (E) 和 lodash (F,G,H) 解决方案中等/相当快
解决方案 JSON.parse/stringify(K) 相当慢
解决方案 D 和 U 在所有浏览器上都很慢
https://i.stack.imgur.com/mCte1.png
深拷贝的结果
解决方案 Q 在所有浏览器上最快
jQuery (L) 和 lodash (J) 中等速度
解决方案 JSON.parse/stringify(K) 相当慢
解决方案 U 在所有浏览器上最慢
lodash (J) 和解决方案 U crash on Chrome for 1000 level deep object
https://i.stack.imgur.com/KxBqv.png
细节
对于选择的解决方案:A B C(my) D E F G H I J K L M N O P Q R S T U,我执行 4 次测试
浅小:具有 10 个非嵌套字段的对象 - 您可以在此处运行它
浅大:具有 1000 个非嵌套字段的对象 - 您可以在此处运行它
deep-small:具有 10 个级别嵌套字段的对象 - 您可以在此处运行它
deep-big:具有 1000 个级别嵌套字段的对象 - 您可以在此处运行它
测试中使用的对象显示在下面的代码片段中
let obj_ShallowSmall = { field0: false, field1: true, field2: 1, field3: 0, field4: null, field5: [], field6: {}, field7: "text7", field8: "text8", } let obj_DeepSmall = { level0: { level1: { level2: { level3: { level4: { level5: { level6: { level7: { level8: { level9: [[[[[[[[[['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_ShallowSmall)); console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall)); console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig)); console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig)); // ------------------ // 帮助 // ------------------ function getField(k) { let我=k%10;如果(i==0)返回假;如果(i==1)返回真; if(i==2) 返回 k;如果(i==3)返回 0; if(i==4) 返回空值; if(i==5) 返回 []; if(i==6) 返回 {};如果(i>=7)返回“文本”+k; } function genDeepObject(N) { // 生成:{level0:{level1:{...levelN: {end:[[[...N-times...['abc']...]]] } }}...}}} 让 obj={};让 o=obj;让 arr = [];让 a=arr; for(让 i=0; i
下面的代码片段展示了经过测试的解决方案并显示了它们之间的差异
函数 A(obj) { return {...obj} } 函数 B(obj) { return Object.assign({}, obj); } function C(obj) { return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), {}) } function D(obj) { let copyOfObject = {}; Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj));返回副本对象; } function E(obj) { return jQuery.extend({}, obj) // 浅层 } function F(obj) { return _.clone(obj); } 函数 G(obj) { return _.clone(obj,true); } 函数 H(obj) { return _.extend({},obj); } 函数 I(obj) { if (null == obj || "object" != typeof obj) 返回 obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } 返回副本; } 函数 J(obj) { return _.cloneDeep(obj,true); } 函数 K(obj) { 返回 JSON.parse(JSON.stringify(obj)); } function L(obj) { return jQuery.extend(true, {}, obj) // deep } function M(obj) { if(obj == null || typeof(obj) != 'object') return obj; var temp = new obj.constructor(); for(obj 中的变量键) temp[key] = M(obj[key]);返回温度; } function N(obj) { let EClone = function(obj) { var newObj = (obj instanceof Array) ? []:{}; for (var i in obj) { if (i == 'EClone') 继续; if (obj[i] && typeof obj[i] == "object") { newObj[i] = EClone(obj[i]); } 否则 newObj[i] = obj[i] } return newObj; };返回 EClone(obj); };函数 O(obj) { if (obj == null || typeof obj != "object") return obj; if (obj.constructor != Object && obj.constructor != Array) 返回 obj; if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function || obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean) 返回新的 obj.constructor(obj);让 to = new obj.constructor(); for (var name in obj) { to[name] = typeof to[name] == "undefined" ? O(obj[name], null) : to[name]; } 还给; } function P(obj) { function clone(target, source){ for(let key in source){ // 使用 getOwnPropertyDescriptor 代替 source[key] 来防止触发 setter/getter。让描述符 = Object.getOwnPropertyDescriptor(source, key); if(descriptor.value instanceof String){ target[key] = new String(descriptor.value); } else if(descriptor.value instanceof Array){ target[key] = clone([], descriptor.value); } else if(descriptor.value instanceof Object){ 让原型 = Reflect.getPrototypeOf(descriptor.value);让 cloneObject = clone({}, descriptor.value); Reflect.setPrototypeOf(cloneObject, 原型);目标[键] = 克隆对象; } else { Object.defineProperty(目标,键,描述符); } } 让原型 = Reflect.getPrototypeOf(source); Reflect.setPrototypeOf(target, 原型);返回目标; } 返回克隆({},obj); } 函数 Q(obj) { var 复制; // 处理 3 种简单类型,null 或 undefined if (null == obj || "object" != typeof obj) return obj; // 处理日期 if (obj instanceof Date) { copy = new Date();复制.setTime(obj.getTime());返回副本; } // 处理数组 if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = Q(obj[i]); } 返回副本; } // 处理对象 if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = Q(obj[attr]); } 返回副本; } throw new Error("无法复制 obj!不支持它的类型。"); } 函数 R(obj) { const gdcc = "__getDeepCircularCopy__"; if (obj !== Object(obj)) { 返回 obj; // 原始值 } var set = gdcc in obj, cache = obj[gdcc], result; if (set && typeof cache == "function") { return cache(); } // else obj[gdcc] = function() { 返回结果; }; // 覆盖 if (obj instanceof Array) { result = []; for (var i=0; i
下面是 Chrome 用于浅大对象的示例结果
https://i.stack.imgur.com/zluC1.png
使用 Lodash:
var y = _.clone(x, true);
_.cloneDeep(x)
,因为它本质上与上面相同,但读起来更好。
在 ES-6 中,您可以简单地使用 Object.assign(...)。前任:
let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);
这里有一个很好的参考:https://googlechrome.github.io/samples/object-assign-es6/
let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";
) 中检查它
obj
的属性的原始类型的值确实被克隆了。对象本身的属性值不会被克隆。
对克隆简单对象感兴趣:
JSON.parse(JSON.stringify(json_original));
来源:How to copy JavaScript object to new variable NOT by reference?
您可以使用一行代码克隆一个对象并从前一个对象中删除任何引用。只需这样做:
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();
};
}
Object.create(...)
似乎绝对是要走的路。
Object.hasOwnProperty
添加解释?这样人们就知道如何防止搜索原型链接。
text
成员。您不是在制作副本,只是在 obj2 上找不到成员时推迟到原型链。
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)
ES6 解决方案,如果你想(浅)克隆一个类实例而不仅仅是一个属性对象。
let cloned = Object.assign({}, obj)
有何不同?
obj
是类实例时, Object.assign()
不会克隆例如类方法(因为它们不可枚举)。
老问题的新答案!如果您有幸将 ECMAScript 2016 (ES6) 与 Spread Syntax 一起使用,这很容易。
keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}
这为对象的浅拷贝提供了一种干净的方法。制作深层副本,即为每个递归嵌套对象中的每个值创建一个新副本,需要上述较重的解决方案。
JavaScript 不断发展。
var obj = {'key1': 'value1'};
var array = [...obj]; // TypeError: obj is not iterable
我认为有一个简单而有效的答案。在深度复制中,有两个问题:
保持属性相互独立。并使方法在克隆对象上保持活动状态。
所以我认为一个简单的解决方案是首先序列化和反序列化,然后对其进行分配以复制函数。
let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);
虽然这个问题有很多答案,但我希望这个也有帮助。
cloneDeep
。
对于深拷贝和克隆,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}}
结构化克隆
2022 年更新:structuredClone()
全局函数已在 Node 17、Deno 1.14 和大多数主要浏览器中可用(请参阅 Can I Use)。
您可以使用 HTML 标准包含的相同结构化克隆机制在领域之间发送数据。
const clone = structuredClone(original);
有关详细信息,请参阅 the other answer。
(以下主要是整合了@Maciej Bukowski、@A. Levy、@Jan Turoň、@Redu的回答,以及@LeviRoberts、@RobG的评论,多谢他们! !!)
深拷贝? - 是的! (大部分);
浅拷贝? - 不! (Proxy
除外)。
我真诚地欢迎大家测试 clone()
。
此外,defineProp()
旨在轻松快速地(重新)定义或复制任何类型的描述符。
功能
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:
_object = copyFn(object)
break
case RegExp:
_object = new RegExp(object)
break
default:
switch (Object.prototype.toString.call(object.constructor)) {
// // Stem from:
case "[object Function]":
switch (object[Symbol.toStringTag]) {
case undefined:
_object = cloneObject(object) // `class`
break
case "AsyncFunction":
case "GeneratorFunction":
case "AsyncGeneratorFunction":
_object = copyFn(object)
break
default:
_object = object
}
break
case "[object Undefined]": // `Object.create(null)`
_object = cloneObject(object)
break
default:
_object = object // `Proxy`
}
}
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 convertFnToStr(fn) {
let fnStr = String(fn)
if (fn.name.startsWith("[")) // isSymbolKey
fnStr = fnStr.replace(/\[Symbol\..+?\]/, '')
fnStr = /^(?!(async )?(function\b|[^{]+?=>))[^(]+?\(/.test(fnStr)
? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr
return fnStr
}
function copyFn(fn) {
const newFn = new Function(`return ${convertFnToStr(fn)}`)()
copyPropDescs(newFn, fn)
return newFn
}
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
})
}
// 测试
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 - More kinds of functions")
const fnsForTest = {
f(_) { return _ },
func: _ => _,
aFunc: async _ => _,
async function() { },
async asyncFunc() { },
aFn: async function () { },
*gen() { },
async *asyncGen() { },
aG1: async function* () { },
aG2: async function* gen() { },
*[Symbol.iterator]() { yield* Object.keys(this) }
}
console.log(Reflect.ownKeys(fnsForTest).map(k =>
`${String(k)}:
${fnsForTest[k].name}-->
${String(fnsForTest[k])}`
).join("\n"))
const normedFnsStr = `{
f: function f(_) { return _ },
func: _ => _,
aFunc: async _ => _,
function: async function() { },
asyncFunc: async function asyncFunc() { },
aFn: async function () { },
gen: function* gen() { },
asyncGen: async function* asyncGen() { },
aG1: async function* () { },
aG2: async function* gen() { },
[Symbol.iterator]: function* () { yield* Object.keys(this) }
}`
const copiedFnsForTest = clone(fnsForTest)
console.log("fnsForTest:", fnsForTest)
console.log("fnsForTest (copied):", copiedFnsForTest)
console.log("fnsForTest (normed str):", eval(`(${normedFnsStr})`))
console.log("Comparison of fnsForTest and its clone:",
Reflect.ownKeys(fnsForTest).map(k =>
[k, fnsForTest[k] === copiedFnsForTest[k]]
)
)
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)
)
参考
对象.create() | MDN Object.defineProperties() | MDN 可枚举性和属性所有权 | MDN TypeError: 循环对象值 | MDN
使用的语言技巧
有条件地向对象添加道具
Symbol("a") === Symbol("a")
是 false
,那么 clone(Symbol("a"))
不应该使用 Symbol(object.description)
创建一个新符号吗?或者这会对众所周知的符号产生太奇怪的影响?
(new Map)[Symbol.iterator]
vs (new Map)[Symbol(Symbol.iterator.description)]
。
使用 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}}}
https://i.stack.imgur.com/vmysI.png
深拷贝: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}}}
https://i.stack.imgur.com/z0M6n.png
Object.getOwnPropertyDescriptors
更好。structuredClone(object)
适用于节点 v17.0.0 及更高版本。