我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
当前回答
这是一个没有Object.assign()陷阱的现代解决方案(不通过引用复制):
const cloneObj = (obj) => {
return Object.keys(obj).reduce((dolly, key) => {
dolly[key] = (obj[key].constructor === Object) ?
cloneObj(obj[key]) :
obj[key];
return dolly;
}, {});
};
其他回答
要处理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字符串/解析的速度,但它确实适用于我测试的简单(循环)对象。
好的,我知道它有很多答案,但没有人指出,EcmaScript5有赋值方法,在FF和Chrome上工作,它复制可枚举的,并且拥有财产和符号。
对象指定(Object Assign)
您可以使用函数闭包来获得深度复制的所有好处,而无需深度复制。这是一个非常不同的范例,但效果很好。不要试图复制现有对象,只要在需要时使用函数实例化新对象即可。
首先,创建一个返回对象的函数
function template() {
return {
values: [1, 2, 3],
nest: {x: {a: "a", b: "b"}, y: 100}
};
}
然后创建一个简单的浅层复制函数
function copy(a, b) {
Object.keys(b).forEach(function(key) {
a[key] = b[key];
});
}
创建一个新对象,并将模板的财产复制到其中
var newObject = {};
copy(newObject, template());
但上述复制步骤不是必需的。您需要做的就是:
var newObject = template();
现在您有了一个新对象,请测试它的财产:
console.log(Object.keys(newObject));
这将显示:
["values", "nest"]
是的,这些是newObject自己的财产,而不是对另一个对象上财产的引用。让我们检查一下:
console.log(newObject.nest.x.b);
这将显示:
"b"
newObject获得了模板对象的所有财产,但没有任何依赖链。
http://jsbin.com/ISUTIpoC/1/edit?js安慰
我添加此示例是为了鼓励一些讨论,因此请添加一些评论:)
我认为,在没有库的情况下,缓存的重复性是最好的。
被低估的WeakMap涉及到循环的问题,其中存储对新旧对象的引用可以帮助我们很容易地重建整个树。
我阻止了DOM元素的深度克隆,可能您不想克隆整个页面:)
function deepCopy(object) {
const cache = new WeakMap(); // Map of old - new references
function copy(obj) {
if (typeof obj !== 'object' ||
obj === null ||
obj instanceof HTMLElement
)
return obj; // primitive value or HTMLElement
if (obj instanceof Date)
return new Date().setTime(obj.getTime());
if (obj instanceof RegExp)
return new RegExp(obj.source, obj.flags);
if (cache.has(obj))
return cache.get(obj);
const result = obj instanceof Array ? [] : {};
cache.set(obj, result); // store reference to object before the recursive starts
if (obj instanceof Array) {
for(const o of obj) {
result.push(copy(o));
}
return result;
}
const keys = Object.keys(obj);
for (const key of keys)
result[key] = copy(obj[key]);
return result;
}
return copy(object);
}
一些测试:
// #1
const obj1 = { };
const obj2 = { };
obj1.obj2 = obj2;
obj2.obj1 = obj1; // Trivial circular reference
var copy = deepCopy(obj1);
copy == obj1 // false
copy.obj2 === obj1.obj2 // false
copy.obj2.obj1.obj2 // and so on - no error (correctly cloned).
// #2
const obj = { x: 0 }
const clone = deepCopy({ a: obj, b: obj });
clone.a == clone.b // true
// #3
const arr = [];
arr[0] = arr; // A little bit weird but who cares
clone = deepCopy(arr)
clone == arr // false;
clone[0][0][0][0] == clone // true;
注意:我使用常量、for of循环、=>运算符和WeakMaps来创建更重要的代码。当前的浏览器支持此语法(ES6)
A.Levy的答案几乎是完整的,这是我的一点贡献:有一种方法可以处理递归引用,请看下面这行
如果(this[attr]==this)copy[attr]=复制;
如果对象是XMLDOM元素,则必须改用cloneNode
if(this.cloneNode)返回this.clone节点(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注释。