Create copy of multi-dimensional array, not reference - JavaScript
Possible Duplicate:
What is the most efficient way to clone a JavaScript object?
这也被称为"深度复制",我在上面找到了一些文章。最接近的似乎是这一个,但它是为jquery-我正在尝试这样做没有一个库。
我还看到,在两个地方,可以做如下事情:
1
| arr2 = JSON.decode(JSON.encode(arr1)); |
但这显然是低效的。还可以单独循环和复制每个值,并在所有数组中循环。这看起来既累人又低效。
那么,复制一个javascript多维数组[[a],[b],]最有效的非库方法是什么?如果必要的话,我对"非IE"方法非常满意。
谢谢!
- 你需要它有多高的效率?您是在客户机中一次又一次地这样做(还是在服务器端类似于节点)?JSON Stringify->Parse方法非常灵活,即使不是最有效的方法。
- 否则,深度复制意味着递归循环…
- 您的结构将保存哪些类型的数据?它只是数组还是其他对象?知道你的结构有多深吗?
- …还有什么要处理的循环引用吗?
- 我肯定会根据JSON decode+encode对备选方案进行基准测试。仅仅用字符串来解码可能看起来很蹩脚,但这是用本机优化的代码来完成的——最终可能会使它更快。
- 它将发生在另一个循环中,在这个循环中,我创建元素并附加单个数组片段(例如,elem.myparam = arr[0][0],它本身就是一个数组)。和客户端。所以"最有效的可能"是不错的。数组主要包含其他数组、整数、字符串和偶尔出现的函数。没有对象。
- @Michaelberkowski我遇到的问题是,在某些情况下,我要"附加"的数组片段中的一个值需要更改,如果我更改了它,中引用的数组将更改,而我需要它不更改,因为我稍后将再次需要它。
- @幸运的是,我没有需要处理的循环引用。
- @兰迪霍尔:如果我理解的话,结构深度不超过一个数组(就像你在问题中展示的那样)?
- 嗯,不完全是。在我的实现中,我只需要一次处理一个数组数组(在稍后调用它们之前,对其他子数组的引用是可以的,此时我只需要"复制"另一个数组),但这可能是无限深的层次。
- @兰迪霍尔:啊,我想我明白了。如果您在任何给定的时间只处理一个层次的深度,如果它们是实际的数组,那么我只需迭代当前数组,并在嵌套数组上使用.slice()构建一个新的数组。会非常快的。
- @我很懒惰,我从来没有正确理解过.slice()在复制数组时所做的工作,你有一个便宜又肮脏的例子吗?这也许是一个很好的答案=)
- @ihateLazy:要避免循环引用,请参见此问题
- @兰迪霍尔:当然,我刚才准备好了一个答案,所以我会继续发布。但是.slice()基本上只是创建一个内容相同的新数组,所以您需要手动创建一个新数组,然后迭代旧数组,并对嵌套数组进行切片。到了进入下一个层次的时候,你也会这样做。
- 我会给你们一点假高潮,因为你们是如此出色的运动。它目前只在Chrome中工作(到目前为止我已经测试过了),但是。有点酷。它目前正在使用一个循环来分配所述数组的单个值。检查它zeroalition.com
- @我很懒惰地来到你附近的一个Github(几个星期后,哈哈)。
- 如果您不将对象作为元素来处理(或不想复制它们),可以使用:matrix.map((row) => [...row]);。
因为这听起来像是在处理一个深度未知的数组,但是在任何给定时间只需要在一个深度处理它们,那么使用.slice()将是简单而快速的。
1 2 3 4
| var newArray = [];
for (var i = 0; i < currentArray.length; i++)
newArray[i] = currentArray[i].slice(); |
或者使用.map()代替for循环:
1 2 3
| var newArray = currentArray.map(function(arr) {
return arr.slice();
}); |
因此,这将迭代当前数组,并构建嵌套数组的浅副本的新数组。当你进入下一个深度层次时,你也会做同样的事情。
当然,如果有数组和其他数据的混合,那么您需要在切片之前测试它是什么。
- 我猜在大多数浏览器中,带有额外函数调用的map速度要慢得多,尽管可以通过内联进行优化,然后性能可能会更好。
- @伯吉:是的,地图版本会有一点影响,但它很好,很干净。
- 哇,地图真干净
- 目前我们只需要调用.slice(0)来克隆一个数组。使用.slice()可以,但.slice(0)更快。
- 现在你可以做ES6速记,使它更简洁。var newArray = currentArray.map(arr => arr.slice());
我不知道JSON.stringy和JSON.parse比encode和decode好多少,但你可以试试:
1
| JSON.parse(JSON.stringify(array)); |
我发现了其他一些东西(尽管我会稍微修改一下):
http://www.xenoveritas.org/blog/xeno/the-correct-way-to-clone-javascript-arrays
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function deepCopy(obj) {
if (typeof obj == 'object') {
if (isArray(obj)) {
var l = obj.length;
var r = new Array(l);
for (var i = 0; i < l; i++) {
r[i] = deepCopy(obj[i]);
}
return r;
} else {
var r = {};
r.prototype = obj.prototype;
for (var k in obj) {
r[k] = deepCopy(obj[k]);
}
return r;
}
}
return obj;
} |
- 这两种方法的缺点是它们不处理自引用循环——这可能重要,也可能不重要(不确定JSON版本将如何处理它,但第二个示例将无限循环)
- @杰夫,我相信你,但你能举个(小)例子吗?只是想了解
- 每次调用deepCopy时,var x = {}; x.y = x;都会命中else语句,而json-just-plain无法处理自引用——它本质上是基于树的
- 我不是说你的回答不好,(如果你知道你没有自荐的话那就好了)但这不是完全的一般性的
- r.prototype = obj.prototype;应该做什么?我觉得很不对劲
- @Bergi看起来像是一次性复制原型函数。这种类型的循环函数似乎是最流行的解决方案,但我希望较新的浏览器有一些隐藏的宝石这种类型的东西。
- @事实上,这很讨厌。原型没有在for循环中迭代(因为它是不可数的),所以它是被复制的,因为它(作为它自己的对象)是被复制的,但不是被深度复制的!但是,您可能不希望它被深度复制,因为它是原型。狡猾的…
- @兰迪霍尔,杰夫:不,不是。它只是创建了一个名为prototype的属性。这并不像__proto__那样设置继承。正确的方法:Object.create(Object.getPrototypeOf(obj));,如@danny's answer所示。
- @Randyhall啊,我看到了……我的浏览器刚刚经历了无限循环。但我知道这不是最好的答案,我只是想发布一些"解决方案"。我真的发现谷歌还有5个"解决方案",我不知道哪一个最好。但既然OP已经帮助明确了他们需要什么,你们已经找到了一个更好的解决方案。
- @是的,我不会说谎,我从我提供的链接中复制粘贴,没有考虑。我开始研究代码,意识到我会改变一些东西(但我忽略了prototype部分)。我希望我能更多地了解原型和构造函数,了解更多,但感谢您正确地复制原型。
当你要求性能时,我想你也会选择非通用的解决方案。要复制具有已知级别数的多维数组,您应该使用最简单的解决方案,一些嵌套for循环。对于二维数组,它只是如下所示:
1 2 3 4
| var len = arr.length,
copy = new Array(len); // boost in Safari
for (var i=0; i<len; ++i)
copy[i] = arr[i].slice(0); |
要扩展到更高维度的数组,请使用递归或嵌套for循环!
本机slice方法比自定义for循环更有效,但它不创建深度副本,因此我们只能在最低级别使用它。
任何不访问同一个节点两次的递归算法的效率都将与使用javascript(至少在浏览器中)所获得的效率相当——在某些情况下,在其他语言中,复制内存块可能会使您逃脱惩罚,但显然javascript没有这种能力。
我建议找一个已经完成了这项工作的人,并使用他们的实现来确保你做的对——只需要定义一次。