x
。我想将它作为对象 y
复制,使得对 y
的更改不会修改 x
。我意识到,复制从内置 JavaScript 对象派生的对象会导致额外的、不必要的属性。这不是问题,因为我要复制的是我自己通过字面量构造的对象。如何正确地克隆 JavaScript 对象?
x
。我想将它作为对象 y
复制,使得对 y
的更改不会修改 x
。我意识到,复制从内置 JavaScript 对象派生的对象会导致额外的、不必要的属性。这不是问题,因为我要复制的是我自己通过字面量构造的对象。有一个名为结构化克隆的新JS标准。它在许多浏览器中都可以使用(请参见Can I Use)。
const clone = structuredClone(object);
在JavaScript中,对于任何对象来说,这都不是简单或直接的。您将遇到从对象的原型中错误地挑选出应该留在原型中而不是复制到新实例中的属性的问题。例如,如果您正在为Object.prototype
添加一个clone
方法,就像某些答案所描述的那样,您需要明确跳过该属性。但是,如果有其他附加方法添加到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);
< p > d1
的日期字符串将比 d2
慢 5 秒。使一个 Date
与另一个相同的方法是调用 setTime
方法,但这仅适用于 Date
类。我认为没有绝对可靠的通用解决方案,尽管我很乐意被证明错误!
当我需要实现通用深度复制时,最终妥协的方法是假设我只需要复制纯 Object
、Array
、Date
、String
、Number
或 Boolean
。最后三种类型是不可变的,所以我可以执行浅复制而不担心它会改变。我进一步假设在 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对象,但只要您不假设它可以适用于任何您投入其中的内容,它可能对许多目的足够。
Object.getOwnPropertyDescriptors
更好。 - Sebastian SimonstructuredClone(object)
,你需要安装至少 17.0.29 版本的 Node.js。你可以使用以下命令:npm i --save-dev @types/node@17.0.29
。 - DariusVDate
、函数、undefined
、RegExp
或 Infinity
,那么一个非常简单的一行代码就可以实现深拷贝:JSON.parse(JSON.stringify(object))
。
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()
这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。
另请参见有关浏览器结构化克隆算法的文章,当在主线程和工作线程之间发送消息时使用此算法。它还包含一个用于深层复制的函数。
a = {}; b = {}; a == b
的结果是 false
。但在执行 a = b
后,结果变为 true
,因为它不仅相同而且是同一个对象。 - heinob在ECMAScript 6中,有一个Object.assign方法,它可以将一个对象的所有可枚举自有属性的值复制到另一个对象中。例如:
var x = {myProp: "value"};
var y = Object.assign({}, x);
但是要注意这是一份浅拷贝 - 嵌套对象仍将作为引用被复制。
使用 jQuery,你可以使用 extend 方法进行 浅拷贝:
var copiedObject = jQuery.extend({}, originalObject)
对于copiedObject
的后续更改不会影响originalObject
,反之亦然。
或者进行深层复制:
var copiedObject = jQuery.extend(true, {}, originalObject)
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;
}
});
}
objA = objB;
会带来各种麻烦。这个方法似乎解决了问题,至少目前是这样的... - WinEunuuchs2Unix有很多答案,但没有一个提到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
及其所有子元素、它们的子元素等等。但由于我不是普通开发人员,我的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
。
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
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';
递归和 循环结构
不太兼容... RangeError: Maximum call stack size exceeded
和同事争论了一番后,老板让我们说明情况,然后通过搜索找到了一个简单的解决方案。它被称为Object.create
。
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
这个解决方案在一段时间前被添加到了Javascript中,甚至可以处理循环结构
。
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"
}
}
...你看,它在嵌套结构内部没有起作用。
在旧浏览器中(如IE 8)有一个对Object.create
的填充。这是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中执行深拷贝时,如何避免由于属性为“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
和circ
的instance
更改为Object
。
共享叶子的树的结构不会被复制,它们将成为两个独立的叶子:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
最后一个使用递归和缓存的解决方案可能不是最好的,但它是对象的真正深拷贝。它处理简单的属性
、循环结构
和嵌套对象
,但是在克隆时会弄乱它们的实例。
if (o instanceof Array) { /*...*/ }
:else if (o instanceof Set) { result = new Set(); for (var i of o) { result.add(i); } }
- watery如果您不介意使用浅复制,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
来进行克隆:
答案主要取决于您使用的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
实现的是浅拷贝(就像spread操作符、@Alizera一样)。 - Bogdan D
mObj=JSON.parse(JSON.stringify(jsonObject));
。 - Lord Loh.Object.create(o)
,它完全满足了作者的要求。 - froginvasionvar x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2;
执行完这段代码后,y.deep.key
的值也会变成2,因此 Object.create 不能用于克隆对象。 - Ruben Stolk