我有一个对象 x
。我想将它复制为对象 y
,这样对 y
的更改不会修改 x
。我意识到复制从内置 JavaScript 对象派生的对象会导致额外的、不需要的属性。这不是问题,因为我正在复制我自己的文字构造对象之一。
如何正确克隆 JavaScript 对象?
原文由 soundly_typed 发布,翻译遵循 CC BY-SA 4.0 许可协议
我有一个对象 x
。我想将它复制为对象 y
,这样对 y
的更改不会修改 x
。我意识到复制从内置 JavaScript 对象派生的对象会导致额外的、不需要的属性。这不是问题,因为我正在复制我自己的文字构造对象之一。
如何正确克隆 JavaScript 对象?
原文由 soundly_typed 发布,翻译遵循 CC BY-SA 4.0 许可协议
如果您不在对象中使用 Date
s、函数、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()
这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。
另请参阅 这篇关于浏览器的结构化克隆算法的文章,该算法 在向工作人员发送消息和从工作人员发送消息时使用。它还包含深度克隆功能。
原文由 heinob 发布,翻译遵循 CC BY-SA 4.0 许可协议
10 回答11.2k 阅读
5 回答4.8k 阅读✓ 已解决
4 回答3.1k 阅读✓ 已解决
2 回答2.7k 阅读✓ 已解决
3 回答2.3k 阅读✓ 已解决
3 回答2.1k 阅读✓ 已解决
2 回答2.6k 阅读✓ 已解决
2022 年更新
有一个称为 结构化克隆 的新 JS 标准。它适用于所有浏览器:
旧答案
对 JavaScript 中的任何对象执行此操作都不会简单或直接。您将遇到错误地从对象原型中获取属性的问题,这些属性应该留在原型中而不是复制到新实例中。例如,如果您要向
Object.prototype
添加一个clone
方法,正如一些答案所描述的那样,您将需要显式跳过该属性。但是,如果在Object.prototype
或其他中间原型中添加了您不知道的其他附加方法怎么办?在这种情况下,您将复制不应该复制的属性,因此您需要使用hasOwnProperty
方法检测不可预见的非本地属性。除了不可枚举的属性之外,当您尝试复制具有隐藏属性的对象时,您还会遇到更棘手的问题。例如,
prototype
是函数的隐藏属性。此外,对象的原型由属性__proto__
引用,该属性也是隐藏的,并且不会被迭代源对象属性的 for/in 循环复制。我认为__proto__
可能特定于 Firefox 的 JavaScript 解释器,并且在其他浏览器中可能有所不同,但你明白了。并非所有事物都是可枚举的。如果您知道它的名称,您可以复制隐藏的属性,但我不知道有什么方法可以自动发现它。寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果您的源对象的原型是
Object
,那么只需使用{}
创建一个新的通用对象即可,但如果源的原型是Object
的某个后代,那么您将丢失该原型中使用跳过的其他成员hasOwnProperty
过滤器,或者在原型中,但一开始就无法枚举。一种解决方案可能是调用源对象的constructor
属性来获取初始复制对象,然后复制属性,但是您仍然不会获得不可枚举的属性。例如,一个Date
对象将其数据存储为隐藏成员:d1
的日期字符串将比d2
晚 5 秒。使一个Date
与另一个 Date 相同的一种方法是调用setTime
方法,但这是特定于Date
类的。我认为这个问题没有万无一失的通用解决方案,尽管我很乐意犯错!当我不得不实现一般的深度复制时,我最终妥协了,假设我只需要复制一个普通的
Object
、Array
、Date
、String
、Number
或Boolean
。最后 3 种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。我进一步假设Object
或Array
中包含的任何元素也将是该列表中的 6 种简单类型之一。这可以通过如下代码来完成:只要对象和数组中的数据形成树形结构,上述函数就可以充分适用于我提到的 6 种简单类型。也就是说,对象中对相同数据的引用不超过一个。例如:
它不能处理任何 JavaScript 对象,但它可能足以满足多种用途,只要您不认为它只适用于您扔给它的任何东西。