互联网上的大多数解决方案都有几个问题。因此,我决定进行跟进,包括为什么不接受接受的答案。
起始情况
我想复制Object
包含所有子代及其子代的Javascript 。但是由于我不是一个普通的开发人员,所以我Object
拥有正常的 properties
,circular structures
甚至nested objects
。
因此,让我们创建一个circular structure
和nested object
第一个。
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
让我们将所有内容放在一起Object
命名为a
。
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甚至是handles的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
Object.create
就像IE 8一样,在较旧的浏览器中也有一个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
与本机解决方案相同的问题,但输出会差一些。
更好(但不是完美)的解决方案
进行深入研究时,我发现了与此问题类似的问题(在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
要求相匹配,但仍有一些小问题,包括不断变化instance
的nested
和circ
给Object
。
共享叶子的树的结构不会被复制,它们将成为两个独立的叶子:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
结论
最后一种使用递归和缓存的解决方案可能不是最好的,但它是对象的真正的深层复制。它操作简单properties
,circular structures
并且nested object
,却会同时克隆弄乱它们的实例。
jsfiddle