在JavaScript中复制数组的最快方法-切片与“ for”循环


634

为了在JavaScript中复制数组,请使用以下哪项更快?

切片方法

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包含对对象的引用,则不会克隆对象,但只会复制引用,因此两个数组都将引用相同的对象。但这不是这个问题的重点。

我只问速度。


3
jsben.ch/#/wQ9RU <=克隆阵列的最常用方法的基准
EscapeNetscape

Answers:


776

至少有5种(!)克隆数组的方法:

  • 切片
  • Array.from()
  • 康卡特
  • 点差算子(FASTEST)

有一个很棒的BENCHMARKS线程,提供以下信息:

  • 眨眼的浏览器slice()是最快的方法,concat()是有点慢,并且while loop是2.4倍慢。

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

2016年7月仍然如此。

下面是简单的脚本,您可以将其复制粘贴到浏览器的控制台中,然后运行几次以查看图片。它们输出毫秒,越低越好。

while循环

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

48
@ cept0没有情绪,只是基准jsperf.com/new-array-vs-splice-vs-slice/31

2
@丹那又怎样?您的测试用例结果:每晚Firefox 30仍然比Chrome快230%。检查V8的源代码,splice您会感到惊讶(而...)
mate64,2014年

4
不幸的是,对于短数组,答案却大不相同。例如,在调用每个侦听器之前先克隆它们的数组。这些数组通常很小,通常为1个元素。
gman 2015年

6
您错过了此方法:A.map(function(e){return e;});
wcochran

13
您正在撰写有关眨眼浏览器的文章眨眼不只是一个布局引擎,主要影响HTML渲染,因此不重要吗?我以为我们宁愿在这里谈论V8,Spidermonkey和朋友。只是让我感到困惑的事情。启发我,如果我错了。
Neonit '16

241

从技术上讲slice 最快的方法。但是,如果添加0begin索引,它甚至更快。

myArray.slice(0);

myArray.slice();

http://jsperf.com/cloning-arrays/3


并且myArray.slice(0,myArray.length-1);myArray.slice(0);
jave.web

1
@ jave.web您;已删除数组的最后一个元素。完整副本是array.slice(0)或array.slice(0,array.length)
Marek Marczak,

137

es6方式呢?

arr2 = [...arr1];

23
如果通过babel转换:[].concat(_slice.call(arguments))
CHAN

1
不确定从何arguments而来...我认为您的babel输出正在混淆一些不同的功能。更有可能arr2 = [].concat(arr1)
Sterling Archer

3
@SterlingArcher arr2 = [].conact(arr1)与有所不同arr2 = [...arr1][...arr1]语法会将Hole转换为undefined。例如,arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3
tsh

1
我在浏览器(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
哈里·史蒂文斯

1
仍然不会克隆这样的东西:[{a: 'a', b: {c: 'c'}}]。如果c在“重复的”数组中更改的值,则它将在原始数组中更改,因为它只是参考副本,而不是克隆副本。
Neurotransmitter

44

深度克隆数组或对象的最简单方法:

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

56
初学者的重要说明:因为这取决于JSON,所以这也继承了其局限性。除其他外,这意味着您的数组不能包含undefined或任何functionnull在此JSON.stringify过程中,这两个都将转换为您。其他策略(例如(['cool','array']).slice()不会更改它们)也不会在阵列内深度克隆对象。因此需要权衡。
塞斯·霍尔拉迪

27
性能非常差,不能与DOM,日期,正则表达式,函数...或原型对象等特殊对象一起使用。不支持循环引用。绝对不要使用JSON进行深度克隆。
Yukulélé

17
最糟糕的方法!仅在某些问题无法解决时使用。它很慢,资源很密集,并且具有注释中已经提到的所有JSON限制。无法想象它如何获得25票赞成票。
Lukas Liesis '16

2
它深度复制具有基元的数组,其中属性是具有其他基元/数组的数组。为此,可以。
德雷奈

4
我在浏览器(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
哈里·史蒂文斯

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

3
请解释一下。
杰德·福克斯

8
尽管此代码段可以回答该问题,但它没有提供任何上下文来解释如何或为什么。考虑添加一两个句子来解释您的答案。
brandonscript

32
我讨厌这种评论。很明显它在做什么!
EscapeNetscape

6
简单的盖头的简单答案,无需阅读任何大故事。我喜欢这种答案+1
Achim

15
“我只问速度”-此答案没有显示速度。这是要问的主要问题。brandonscript有一个好处。需要更多信息才能将其视为答案。但是,如果这是一个简单的问题,那将是一个很好的答案。
TamusJRoyce'1

26

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

我在Internet Explorer 8上的结果是156、782和750,这表明slice在这种情况下速度要快得多。


如果您必须非常快地执行此操作,请不要忘记垃圾收集器的额外成本。我正在使用slice为元胞自动机中的每个单元格复制每个邻居数组,这比重用先前的数组并复制值要慢得多。Chrome表示总时间中约有40%用于垃圾收集。
drake7707 2013年

21

a.map(e => e)是这项工作的另一种选择。截至目前.map().slice(0)在Firefox中速度非常快(几乎与一样快),但在Chrome中却没有。

另一方面,如果数组是多维的,则由于数组是对象,而对象是引用类型,所以slice或concat方法都不是治愈方法。因此克隆数组的一种合适方法是Array.prototype.clone()as 的发明。如下。

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));


不错,但是不幸的是,如果数组中有Object,则此方法不起作用:\ JSON.parse(JSON.stringify(myArray))在这种情况下效果更好。
GBMan

17

to克隆阵列的最快方法

我制作了这个非常简单的实用程序函数来测试克隆数组所花费的时间。它不是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));

更新
注意:在所有方法中,深度克隆数组的唯一方法是使用JSON.parse(JSON.stringify(arr))

也就是说,如果您的数组可能包含将返回的函数,请不要使用上面的函数null
感谢@GilEpshtain进行此更新


2
我尝试对您的答案进行基准测试,结果却大不相同:jsben.ch/o5nLG
mesqueeb

@mesqueeb,测试可能会更改,具体取决于您的机器。但是,请随时用测试结果更新答案。干得好!
Lior Elrom '19

我非常喜欢您的答案,但是我尝试了您的测试,并得出arr => arr.slice()了最快的答案。
吉尔·艾普史丹

1
@LiorElrom,由于方法不可序列化,因此您的更新不正确。例如:JSON.parse(JSON.stringify([function(){}]))将输出[null]
Gil Epshtain '19

1
不错的基准。我在2个浏览器中测试了这个在我的Mac:Chrome操作系统版本81.0.4044.113和Safari版本13.1(15609.1.20.111.8)和最快的是传播的操作:[...arr]4.653076171875ms在Chrome和8.565msSafari中。在Chrome中第二快切片功能,arr.slice()6.162109375ms和Safari中第二个是[].concat(arr)13.018ms
edufinn

7

看一下:link。这不是速度,而是舒适。此外,如您所见,您只能在原始类型上使用slice(0)

要创建数组的独立副本,而不是对其的引用副本,可以使用数组切片方法。

例:

要创建数组的独立副本,而不是对其的引用副本,可以使用数组切片方法。

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

复制或克隆对象:

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);

资料来源:链接


1
原始类型注释适用于for在问题环路为好。
user113716

4
如果要复制对象数组,则希望新数组引用相同的对象,而不是克隆对象。
lincolnk 2010年

7

正如@Dan所说:“这个答案很快就过时了。使用基准来检查实际情况”,jsperf有一个具体的答案本身还没有答案:while

var i = a.length;
while(i--) { b[i] = a[i]; }

拥有960,589个操作/秒,亚军a.concat()为578,129个操作/秒,占60%。

这是最新的Firefox(40)64位。


@aleclarson创建了一个新的,更可靠的基准。


1
您应该真正链接jsperf。您正在考虑的那个数组已损坏,因为在每个测试用例中都会创建一个新数组,除了“ while循环”测试。
aleclarson

1
我制作了一个更准确的新jsperf:jsperf.com/clone-array-3
aleclarson

60%是什么?快60%?
彼得·莫滕森

1
@PeterMortensen:587192是〜的960589. 60%(61.1 ...)
SERV-INC

7

ECMAScript 2015与Spread操作员的方式:

基本示例:

var copyOfOldArray = [...oldArray]
var twoArraysBecomeOne = [...firstArray, ..seccondArray]

在浏览器控制台中尝试:

var oldArray = [1, 2, 3]
var copyOfOldArray = [...oldArray]
console.log(oldArray)
console.log(copyOfOldArray)

var firstArray = [5, 6, 7]
var seccondArray = ["a", "b", "c"]
var twoArraysBecomOne = [...firstArray, ...seccondArray]
console.log(twoArraysBecomOne);

参考文献


价差最快的唯一一件事就是打字。与其他方式相比,它的性能要低得多。
XT_Nova

3
请提供有关您的论点的链接。
Marian07年

6

这取决于浏览器。如果您看博客文章Array.prototype.slice与手动数组创建,则可以找到有关每种性能的粗略指南:

在此处输入图片说明

结果:

在此处输入图片说明


1
arguments不是一个适当的数组,他call用来强制slice在集合上运行。结果可能会产生误导。
lincolnk 2010年

是的,我想在我的帖子中提到这些数据现在可能会随着浏览器的改进而改变,但这给出了一个总体思路。
kyndigs 2010年

2
@diugalde我认为将代码作为图片发布的唯一情况是该代码具有潜在危险并且不应被复制粘贴。但是在这种情况下,这很荒谬。
Florian Wendelborn

6

有一个更清洁的解决方案:

var srcArray = [1, 2, 3];
var clonedArray = srcArray.length === 1 ? [srcArray[0]] : Array.apply(this, srcArray);

长度检查是必需的,因为在Array仅使用一个参数调用构造函数时,其行为会有所不同。


2
但这是最快的吗?
克里斯·韦瑟林

14
splice()可能比更具语义。不过说真的,应用几乎是直观的。
Michael Piefel 2014年


3
您可以使用Array.of长度,而忽略长度:Array.of.apply(Array, array)
Oriol

6

请记住,.slice()不适用于二维数组。您将需要一个这样的函数:

function copy(array) {
  return array.map(function(arr) {
    return arr.slice();
  });
}

3
在Javascript中,没有二维数组。只有包含数组的数组。您正在尝试做的是该问题不需要的深层副本
Aloso

5

这取决于数组的长度。如果数组长度<= 1,000,000,则sliceconcat方法所花费的时间大约相同。但是,当您提供更大范围时,concat方法会成功。

例如,尝试以下代码:

var original_array = [];
for(var i = 0; i < 10000000; i ++) {
    original_array.push( Math.floor(Math.random() * 1000000 + 1));
}

function a1() {
    var dup = [];
    var start = Date.now();
    dup = original_array.slice();
    var end = Date.now();
    console.log('slice method takes ' + (end - start) + ' ms');
}

function a2() {
    var dup = [];
    var start = Date.now();
    dup = original_array.concat([]);
    var end = Date.now();
    console.log('concat method takes ' + (end - start) + ' ms');
}

function a3() {
    var dup = [];
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup.push(original_array[i]);
    }
    var end = Date.now();
    console.log('for loop with push method takes ' + (end - start) + ' ms');
}

function a4() {
    var dup = [];
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup[i] = original_array[i];
    }
    var end = Date.now();
    console.log('for loop with = method takes ' + (end - start) + ' ms');
}

function a5() {
    var dup = new Array(original_array.length)
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup.push(original_array[i]);
    }
    var end = Date.now();
    console.log('for loop with = method and array constructor takes ' + (end - start) + ' ms');
}

a1();
a2();
a3();
a4();
a5();

如果将original_array的长度设置为1,000,000,则 slice方法和concat方法所花费的时间大约相同(3-4毫秒,具体取决于随机数)。

如果将original_array的长度设置为10,000,000,则该slice方法将花费60毫秒以上,而该concat方法将花费20毫秒以上。


dup.push是错误的a5dup[i] = 应该使用
4esn0k

3

一个简单的解决方案:

original = [1,2,3]
cloned = original.map(x=>x)

2
        const arr = ['1', '2', '3'];

         // Old way
        const cloneArr = arr.slice();

        // ES6 way
        const cloneArrES6 = [...arr];

// But problem with 3rd approach is that if you are using muti-dimensional 
 // array, then only first level is copied

        const nums = [
              [1, 2], 
              [10],
         ];

        const cloneNums = [...nums];

// Let's change the first item in the first nested item in our cloned array.

        cloneNums[0][0] = '8';

        console.log(cloneNums);
           // [ [ '8', 2 ], [ 10 ], [ 300 ] ]

        // NOOooo, the original is also affected
        console.log(nums);
          // [ [ '8', 2 ], [ 10 ], [ 300 ] ]

因此,为了避免发生这些情况,请使用

        const arr = ['1', '2', '3'];

        const cloneArr = Array.from(arr);

指出cloneNums[0][0]示例中的更改如何将更改传播到nums[0][0]- 是一件很有意义的事,但这是因为nums[0][0]有效地是一个对象,其引用cloneNums已由散布运算符复制到该对象中。就是说,此行为不会影响我们按值(int,string等文字)进行复制的代码。
Aditya MP

1

基准时间!

function log(data) {
  document.getElementById("log").textContent += data + "\n";
}

benchmark = (() => {
  time_function = function(ms, f, num) {
    var z = 0;
    var t = new Date().getTime();
    for (z = 0;
      ((new Date().getTime() - t) < ms); z++)
      f(num);
    return (z)
  }

  function clone1(arr) {
    return arr.slice(0);
  }

  function clone2(arr) {
    return [...arr]
  }

  function clone3(arr) {
    return [].concat(arr);
  }

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

  function clone4(arr) {
    return arr.clone();
  }


  function benchmark() {
    function compare(a, b) {
      if (a[1] > b[1]) {
        return -1;
      }
      if (a[1] < b[1]) {
        return 1;
      }
      return 0;
    }

    funcs = [clone1, clone2, clone3, clone4];
    results = [];
    funcs.forEach((ff) => {
      console.log("Benchmarking: " + ff.name);
      var s = time_function(2500, ff, Array(1024));
      results.push([ff, s]);
      console.log("Score: " + s);

    })
    return results.sort(compare);
  }
  return benchmark;
})()
log("Starting benchmark...\n");
res = benchmark();

console.log("Winner: " + res[0][0].name + " !!!");
count = 1;
res.forEach((r) => {
  log((count++) + ". " + r[0].name + " score: " + Math.floor(10000 * r[1] / res[0][1]) / 100 + ((count == 2) ? "% *winner*" : "% speed of winner.") + " (" + Math.round(r[1] * 100) / 100 + ")");
});
log("\nWinner code:\n");
log(res[0][0].toString());
<textarea rows="50" cols="80" style="font-size: 16; resize:none; border: none;" id="log"></textarea>

自单击按钮以来,基准测试将运行10秒钟。

我的结果:

Chrome(V8引擎):

1. clone1 score: 100% *winner* (4110764)
2. clone3 score: 74.32% speed of winner. (3055225)
3. clone2 score: 30.75% speed of winner. (1264182)
4. clone4 score: 21.96% speed of winner. (902929)

Firefox(SpiderMonkey引擎):

1. clone1 score: 100% *winner* (8448353)
2. clone3 score: 16.44% speed of winner. (1389241)
3. clone4 score: 5.69% speed of winner. (481162)
4. clone2 score: 2.27% speed of winner. (192433)

优胜者代码:

function clone1(arr) {
    return arr.slice(0);
}

优胜者引擎:

SpiderMonkey(Mozilla / Firefox)


1

快速复制JavaScript中的数组的快速方法:

#1: array1copy = [...array1];

#2: array1copy = array1.slice(0);

#3: array1copy = array1.slice();

如果您的数组对象包含一些JSON不可序列化的内容(函数,Number.POSITIVE_INFINITY等),则最好使用

array1copy = JSON.parse(JSON.stringify(array1))


0

您可以遵循此代码。不可变方式数组克隆。这是阵列克隆的完美方法


const array = [1, 2, 3, 4]

const newArray = [...array]
newArray.push(6)
console.log(array)
console.log(newArray)

0

在ES6中,您可以简单地使用Spread语法

例:

let arr = ['a', 'b', 'c'];
let arr2 = [...arr];

请注意,散布运算符会生成一个全新的数组,因此修改一个数组不会影响另一个数组。

例:

arr2.push('d') // becomes ['a', 'b', 'c', 'd']
console.log(arr) // while arr retains its values ['a', 'b', 'c']
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.