在JavaScript中复制数组的最快方法——使用slice和'for'循环对比

775
为了在JavaScript中复制一个数组:以下哪种方法更快?

Slice方法

var dup_array = original_array.slice();

For循环

for(var i = 0, len = original_array.length; i < len; ++i)
   dup_array[i] = original_array[i];

我知道这两种方法都只做浅复制:如果original_array包含对对象的引用,则不会克隆对象,而只会复制引用,因此两个数组将具有对相同对象的引用。 但这不是这个问题的重点。

我只是在询问速度。


5
这是一个用于比较常见数组复制方法性能的基准测试。 - EscapeNetscape
请参见 javascript - Copy array by value - Stack Overflow --(该问题中的某些答案进行了性能比较) - user202729
有人尝试过使用返回所需数组的特定函数进行基准测试吗?例如,const getInitialArray = () => {return [[1, 2], [3, 4]} - rybo111
有人尝试过使用返回所需数组的特定函数进行基准测试吗?例如 const getInitialArray = () => {return [[1, 2], [3, 4]} - undefined
25个回答

903

有至少6种方法可以克隆数组:

  • 循环
  • slice
  • Array.from()
  • concat
  • spread 语法(最快)
  • 使用 map A.map(function(e){return e;});

根据BENCHMARKS thread提供的信息:

  • 对于blink浏览器,slice()是最快的方法,concat()稍微慢一些,而while loop则慢2.4倍。

  • 对于其他浏览器,while loop是最快的方法,因为这些浏览器没有对sliceconcat进行内部优化。

这在2016年7月仍然是正确的。

以下是一些简单的脚本,您可以将其复制并粘贴到浏览器控制台中并运行多次以查看情况。它们输出毫秒数,数值越小越好。

while loop

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = Array(n); 
i = a.length;
while(i--) b[i] = a[i];
console.log(new Date() - start);

切片

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = a.slice();
console.log(new Date() - start);
请注意,这些方法将克隆Array对象本身,但数组内容是通过引用复制的,不会进行深度克隆。
origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true

55
@cept0没有情感,只有基准测试数据 http://jsperf.com/new-array-vs-splice-vs-slice/31 - Dan
3
@Dan 那又怎样呢?你的测试结果显示:Firefox 30 的夜间版仍比 Chrome 快约230%。查看 V8 的源代码以了解 splice,你会感到惊讶(同时...)。 - mate64
5
遗憾的是,对于短数组而言,答案大不相同。例如,在调用每个侦听器之前克隆侦听器数组。这些数组通常很小,通常只有一个元素。 - gman
8
你错过了这个方法:A.map(function(e){return e;});。它的作用是将数组 A 中的每个元素都返回并存储在一个新数组中。 - wcochran
18
你在写关于"blink"浏览器的内容。"blink"不仅仅是一个排版引擎,主要影响HTML渲染,因此并不重要吗?我想我们更应该讨论V8、Spidermonkey和相关技术。这件事有点困惑我。如果我错了,请给我指点一下。 - Neonit
显示剩余14条评论

324

从技术上讲,slice是最快的方式。然而,如果您添加0作为开始索引,则会更快。

myArray.slice(0);

比...更快

myArray.slice();

https://jsben.ch/F0SZ3


myArray.slice(0,myArray.length-1);myArray.slice(0); 更快吗? - jave.web
11
您刚刚删除了数组的最后一个元素。如果要完整复制,请使用array.slice(0)或array.slice(0,array.length)。 - Marek Marczak
这是错误的,至少在我的电脑上并根据您自己的基准测试。 - John Leidegren
4
链接已失效。 - kschiffer
3
有时候,slice() 更快,有时候 slice(0) 更快,但两者仅在极小程度上有所区别(在Firefox 56和最新的基于Chrome的Vivaldi中)。但是 slice(0, length) 总是明显较慢(除了在Firefox 87中它是最快的)。 - f2d
这样做并不是克隆。它仍然是引用复制。 - 1nstinct

190

ES6 的方式怎么样?

arr2 = [...arr1];

28
[].concat(_slice.call(arguments)):将参数转换为数组并合并到一个新数组中。 - CHAN
1
不确定arguments从哪里来...我认为你的babel输出混淆了一些不同的特性。更可能的是arr2 = [].concat(arr1) - Sterling Archer
3
arr2 = [].concat(arr1)arr2 = [...arr1] 是不同的。[...arr1] 语法会将空洞转换为 undefined。例如,arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3 - tsh
2
我在我的浏览器(Chrome 59.0.3071.115)中对比了Dan上面的答案进行了测试。它比.slice()慢了10倍以上。 n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168 - Harry Stevens
4
仍然不会克隆像这样的东西:[{a: 'a', b: {c: 'c'}}]。如果“复制”的数组中更改了“c”的值,它将在原始数组中改变,因为这只是一个引用副本,而不是克隆。 - Neurotransmitter
显示剩余3条评论

54

最简单的深度复制数组或对象的方法:

var dup_array = JSON.parse(JSON.stringify(original_array))

75
初学者需注意:由于该程序依赖于 JSON,因此也继承了它的限制。这意味着你的数组不能包含 undefined 或任何 function。在 JSON.stringify 过程中,这两者都会被转换为 null。其他策略,比如 (['cool','array']).slice() 不会改变它们,但也不会深度克隆数组中的对象。因此需要一个权衡。 - Seth Holladay
35
非常糟糕的性能,不能处理像DOM、日期、正则表达式、函数或原型对象等特殊对象。不支持循环引用。您永远不应该使用JSON进行深层克隆。 - Yukulélé
20
最糟糕的方式!只有在其他所有方法都无法解决问题时才使用。它速度缓慢,消耗资源严重,并且已经出现了评论中提到的所有JSON限制。无法想象它如何获得25个赞。 - Lukas Liesis
4
它可以深度复制包含基本类型的数组,以及属性是包含更多基本类型/数组的数组。对此,它是可以的。 - Drenai
6
我在我的浏览器(Chrome 59.0.3071.115)中对比了Dan上面的答案。它比.slice()慢近20倍。n = 1000 * 1000;start = + new Date();a = Array(n);var b = JSON.parse(JSON.stringify(a));console.log(new Date() - start); // 221 - Harry Stevens
显示剩余2条评论

46

最快的数组克隆方式

我创建了一个非常简单的实用函数来测试克隆数组所需的时间。它不是100%可靠,但可以让您大致了解克隆现有数组所需的时间:

function clone(fn) {
  const arr = [...Array(1000000)];
  console.time('timer');
  fn(arr);
  console.timeEnd('timer');
}

并尝试了不同的方法:

1)   5.79ms -> clone(arr => Object.values(arr));
2)   7.23ms -> clone(arr => [].concat(arr));
3)   9.13ms -> clone(arr => arr.slice());
4)  24.04ms -> clone(arr => { const a = []; for (let val of arr) { a.push(val); } return a; });
5)  30.02ms -> clone(arr => [...arr]);
6)  39.72ms -> clone(arr => JSON.parse(JSON.stringify(arr)));
7)  99.80ms -> clone(arr => arr.map(i => i));
8) 259.29ms -> clone(arr => Object.assign([], arr));
9) Maximum call stack size exceeded -> clone(arr => Array.of(...arr));

更新

  1. 测试是在2018年进行的,因此今天使用当前浏览器很可能会得到不同的结果。
  2. 在所有这些方法中,唯一深度克隆数组的方法是使用 JSON.parse(JSON.stringify(arr))

    尽管如此,如果您的数组可能包含函数,则不要使用上述方法,因为它将返回 null
    感谢 @GilEpshtain 提供的更新.

6
我尝试对你的答案进行基准测试,但得到了非常不同的结果:http://jsben.ch/o5nLG - mesqueeb
@mesqueeb,测试可能会因为你的机器而有所改变。但是,请随意使用你的测试结果更新答案。干得好! - Lior Elrom
1
我非常喜欢你的答案,但是我尝试了你的测试,发现 arr => arr.slice() 是最快的。 - Gil Epshtain
2
@LiorElrom,你的更新不正确,因为方法不可序列化。例如:JSON.parse(JSON.stringify([function(){}]))将输出[null] - Gil Epshtain
2
不错的基准测试。我在我的Mac上使用了2个浏览器进行了测试:Chrome版本81.0.4044.113和Safari版本13.1(15609.1.20.111.8),最快的是扩展操作符[...arr],在Chrome中为4.653076171875ms,在Safari中为8.565ms。在Chrome中第二快的是切片函数arr.slice(),用时为6.162109375ms,在Safari中第二快的是[].concat(arr),用时为13.018ms - edufinn
显示剩余3条评论

36
var cloned_array = [].concat(target_array);

4
请解释这个代码的作用。 - Jed Fox
13
尽管这段代码片段可能回答了问题,但它没有提供任何上下文来解释如何或为什么。考虑添加一两句话来解释你的答案。 - brandonscript
41
我讨厌这种评论。它所做的显而易见! - EscapeNetscape
7
简单问题简单回答,无需冗长篇幅。我喜欢这种回答。+1 - Achim
23
“我只关心速度” - 这个回答没有提供任何关于速度的指示。这是被问者主要在询问的问题。brandonscript说得很好,需要更多的信息才能考虑这是否是一个答案。但如果这是一个更简单的问题,这将是一个很好的回答。 - TamusJRoyce
显示剩余2条评论

26

我做了一个快速演示:http://jsbin.com/agugo3/edit

在Internet Explorer 8上的结果是156、782和750,这表明在这种情况下slice更快。


2
不要忘记垃圾收集器的额外成本,如果你需要快速频繁地执行此操作。我曾经在我的元胞自动机中为每个单元格复制每个邻居数组,使用slice方法时速度非常慢,比重用先前的数组并复制值要慢得多。Chrome浏览器指出,大约40%的总时间用于垃圾回收。 - Drakarah

21

a.map(e => e)是另一种可行的方法。截至今天,.map()在Firefox中非常快(几乎与.slice(0)一样快),但在Chrome中不是。

另一方面,如果数组是多维的,由于数组是对象且对象是引用类型,因此没有任何slice或concat方法可以解决问题...因此,正确克隆数组的一种方法是使用Array.prototype.clone()发明如下所示。

Array.prototype.clone = function(){
  return this.map(e => Array.isArray(e) ? e.clone() : e);
};

var arr = [ 1, 2, 3, 4, [ 1, 2, [ 1, 2, 3 ], 4 , 5], 6 ],
    brr = arr.clone();
brr[4][2][1] = "two";
console.log(JSON.stringify(arr));
console.log(JSON.stringify(brr));


还不错,但是如果你的数组中有对象的话,这种方法就不起作用了 :
在这种情况下,JSON.parse(JSON.stringify(myArray)) 更好用。
- GBMan

18

使用扩展运算符是克隆对象数组最快的方式。

var clonedArray=[...originalArray]
or
var clonedArray = originalArray.slice(0); //with 0 index it's little bit faster than normal slice()

但克隆数组中的对象仍然指向旧的内存位置,因此对克隆数组对象的更改也将更改原始数组。所以

var clonedArray = originalArray.map(({...ele}) => {return ele})

这不仅会创建一个新数组,而且对象也会被克隆。

免责声明: 如果您正在使用嵌套对象,则展开运算符将作为浅层克隆工作。此时最好使用

var clonedArray=JSON.parse(JSON.stringify(originalArray));

1
你是唯一一个注意到内存地址的人。为此,你额外获得了积分!! - JRichardsz
非常感谢您强调内存位置。实际上,这可能是您需要一个“解耦”的数组克隆的主要原因。 - herrstrietzel
1
JSON.parse(JSON.stringify(x)) 总是一个不好的主意。它会导致信息丢失,如果你想克隆结构化信息,完全没有必要通过 文本 进行往返。 - T.J. Crowder

9

请看:链接,这不是关于速度,而是舒适性。此外,正如您所看到的,您只能在原始类型上使用slice(0)

为了制作数组的独立副本而不是其引用的副本,您可以使用数组切片方法。

例如:

To make an independent copy of an array rather than a copy of the refence to it, you can use the array slice method.

var oldArray = ["mip", "map", "mop"];
var newArray = oldArray.slice();

To copy or clone an object :

function cloneObject(source) {
    for (i in source) {
        if (typeof source[i] == 'source') {
            this[i] = new cloneObject(source[i]);
        }
        else{
            this[i] = source[i];
  }
    }
}

var obj1= {bla:'blabla',foo:'foofoo',etc:'etc'};
var obj2= new cloneObject(obj1);

Source: link


2
“primitive types” 的注释同样适用于问题中的 “for” 循环。 - user113716
4
如果我在复制一个对象数组,我期望新的数组引用相同的对象而不是克隆这些对象。 - lincolnk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接