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

如何正确克隆JavaScript对象?


当前回答

有很多答案,但没有一个提到ECMAScript 5中的Object.create,它确实没有给您一个确切的副本,而是将源设置为新对象的原型。

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

在这种继承有用的地方(duh!)源对象不会被修改,因此这两个对象之间的关系不成问题。

例子:

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。

其他回答

对克隆简单对象感兴趣:

JSON.parse(JSON.stringify(JSON_inoriginal));

来源:如何通过引用将JavaScript对象复制到新变量NOT?

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 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

有很多答案,但没有一个提到ECMAScript 5中的Object.create,它确实没有给您一个确切的副本,而是将源设置为新对象的原型。

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

在这种继承有用的地方(duh!)源对象不会被修改,因此这两个对象之间的关系不成问题。

例子:

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。

要处理JSON.stringify无法处理的循环对象,可以引入一个名为JSOG的库,该库将任意图形序列化和反序列化为JSON格式。

var clone = JSOG.parse(JSOG.stringify(original));

尝试使用此技巧修补JSOG以进行克隆可能也很有趣(目前没有时间,但如果有人想尝试一下…):

序列化简单函数:

foo.f = function(a) { return a }
var stringForm = foo.f.toString() // "function (a) { return a }"

反序列化函数:

eval("foo.f = " + stringForm)

需要一些约定(可能是以属性的名称)来标识函数与正则字符串(可能是@func_f)。

当然,如果该函数调用第二个函数,则第二个功能将需要与原始函数一样存在。

然而,如果您要接受来自不可信来源的序列化表单,则上述内容非常危险,但接受来自不可靠来源的任何形式的函数都是危险的,因此如果您对克隆函数感兴趣,则必须已经建立了信任(或者您已经打算编写安全漏洞!)。

免责声明:我没有测试JSOG字符串/解析与JSON字符串/解析的速度,但它确实适用于我测试的简单(循环)对象。