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

Mar*_*aio 594 javascript arrays copy duplicates slice

为了在JavaScript中复制数组:以下哪个更快使用?

切片方法

var dup_array = original_array.slice();
Run Code Online (Sandbox Code Playgroud)

For

for(var i = 0, len = original_array.length; i < len; ++i)
   dup_array[i] = original_array[i];
Run Code Online (Sandbox Code Playgroud)

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

我只询问速度.

Dan*_*Dan 739

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

  • 切片
  • Array.from()
  • CONCAT
  • 传播运营商(最快)

有一个huuuge BENCHMARKS线程,提供以下信息:

  • 对于闪烁浏览器slice()是最快的方法,concat()有点慢,并且while loop慢2.4倍.

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

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);
Run Code Online (Sandbox Code Playgroud)

切片

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = a.slice();
console.log(new Date() - start);
Run Code Online (Sandbox Code Playgroud)

请注意,这些方法将克隆Array对象本身,但是数组内容是通过引用复制的,并且不会被深度克隆.

origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true
Run Code Online (Sandbox Code Playgroud)

  • @ cept0没有情绪,只是基准http://jsperf.com/new-array-vs-splice-vs-slice/31 (46认同)
  • 你正在写关于***blink**浏览器*.不是*blink*只是一个布局引擎,主要影响HTML渲染,因此不重要?我想我们宁愿在这里谈论V8,蜘蛛侠和朋友.只是让我困惑的事情.如果我错了,请启发我. (12认同)
  • 你错过了这个方法:`A.map(function(e){return e;});` (6认同)
  • [可悲的是,对于短阵列,答案是截然不同的](http://jsperf.com/new-array-vs-splice-vs-slice/72).例如,在调用每个侦听器之前克隆一组侦听器.那些数组通常很小,通常是1个元素. (4认同)
  • @Dan那又怎样?您的测试用例结果:Firefox 30每晚仍然比Chrome快〜230%.检查V8的源代码是否有"拼接",你会感到惊讶(而......) (2认同)
  • **警告**:`.slice`、`spread`、`concat`、`Array.from` 不会克隆数组中的对象。使用循环或深度克隆库 (2认同)
  • 对我来说最好的方法是 new_array = JSON.parse( JSON.stringify( old_array ) )。这样所有的引用都被杀死了 (2认同)

Kin*_*rog 226

技术上slice 最快的方式.但是,如果添加0begin索引,它会更快.

myArray.slice(0);
Run Code Online (Sandbox Code Playgroud)

比...更快

myArray.slice();
Run Code Online (Sandbox Code Playgroud)

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

  • @jave.web 你刚刚删除了数组的最后一个元素。完整副本是 array.slice(0) 或 array.slice(0, array.length) (7认同)
  • 链接已失效。 (3认同)
  • https://jsben.ch/56xWo - 有时,`slice()` 更快,有时 `slice(0)` 更快,两者都只有一点点(在 Firefox 56 和最新的 Vivaldi,基于 Chrome 中)。但是 `slice(0, length)` 总是明显慢一些(除了它在 Firefox 87 中是最快的)。 (3认同)

Yuk*_*élé 122

es6方式怎么样?

arr2 = [...arr1];
Run Code Online (Sandbox Code Playgroud)

  • 如果用babel转换:`[] .concat(_slice.call(arguments))` (22认同)
  • @SrlingArcher`arr2 = [].conact(arr1)`与`arr2 = [... arr1]`不同.`[... arr1]`语法会将空洞转换为`undefined`.例如,`arr1 = Array(1); arr2 = [... arr1]; arr3 = [] .concat(arr1); arr2中的0!= = arr3`中的0. (3认同)
  • 我在我的浏览器(Chrome 59.0.3071.115)中针对上面丹的回答测试了这个。它比 .slice() 慢 10 倍以上。`n = 1000*1000; 开始 = + 新日期();a = 数组(n); b = [...a]; 控制台日志(新日期() - 开始);// 168` (2认同)
  • 仍然不会克隆这样的东西:`[{a: 'a', b: {c: 'c'}}]`。如果`c` 的值在“重复”数组中改变,它也会在原始数组中改变,因为它只是一个引用副本,而不是一个克隆。 (2认同)

Vla*_*idi 43

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

var dup_array = JSON.parse(JSON.stringify(original_array))
Run Code Online (Sandbox Code Playgroud)

  • 初学者的重要注意事项:因为这取决于JSON,这也继承了它的局限性.除此之外,这意味着你的数组不能包含`undefined`或任何`function`s.在`JSON.stringify`过程中,这两个都将被转换为`null`.其他策略,例如`(['cool','array']).slice()`不会改变它们,也不会深入克隆数组中的对象.所以有一个权衡. (55认同)
  • 非常糟糕的性能,不适用于DOM,日期,正则表达式,函数...或原型对象等特殊对象.不支持循环引用.您永远不应该使用JSON进行深度克隆. (24认同)
  • 最糟糕的方式!仅在某些问题上使用,所有其他问题都不起作用.它很慢,资源非常紧张,并且已经在评论中提到了所有JSON限制.无法想象如何获得25票. (16认同)
  • 我在浏览器(Chrome 59.0.3071.115)中对Dan的答案进行了测试.它比.slice()慢近20倍.`n = 1000*1000; start = + new Date(); a =数组(n); var b = JSON.parse(JSON.stringify(a))console.log(new Date() - start); // 221` (4认同)
  • 它使用基元深度复制数组,其中属性是具有更多基元/数组的数组.为此,没关系. (2认同)

Saj*_*azy 29

var cloned_array = [].concat(target_array);
Run Code Online (Sandbox Code Playgroud)

  • 我讨厌这种评论.它的作用显而易见! (30认同)
  • "我只询问速度" - 这个答案没有说明速度.这是被问到的主要问题.brandonscript有一个好点.需要更多信息来考虑这个答案.但如果这是一个更简单的问题,这将是一个很好的答案. (14认同)
  • 虽然此代码段可以回答问题,但它不提供任何上下文来解释如何或为什么.考虑添加一两句话来解释你的答案. (8认同)
  • 一个简单的简单回答,没有重要的故事要读.我喜欢这种答案+1 (6认同)
  • 请解释一下这是做什么的. (3认同)

lin*_*lnk 26

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

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

  • 如果您必须非常快地执行此操作,请不要忘记垃圾收集器的额外成本。我使用切片复制元胞自动机中每个单元格的每个邻居数组,它比重用以前的数组并复制值要慢得多。Chrome 表示大约 40% 的总时间用于垃圾收集。 (2认同)

Red*_*edu 19

a.map(e => e)是这项工作的另一种选择.到目前为止,在Firefox中.map()速度非常快(几乎一样快.slice(0)),但在Chrome中则不然.

另一方面,如果一个数组是多维的,因为数组是对象而对象是引用类型,所以切片或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));
Run Code Online (Sandbox Code Playgroud)


Aay*_*rya 18

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

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

但该克隆数组内的对象仍将指向旧的内存位置。因此,对 clonedArray 对象的更改也会更改 orignalArray。所以

var clonedArray = originalArray.map(({...ele}) => {return ele})
Run Code Online (Sandbox Code Playgroud)

这不仅会创建新数组,还会将对象克隆到其中。

如果您正在使用嵌套对象,则免责声明在这种情况下扩展运算符将作为浅克隆工作。那时最好使用

var clonedArray=JSON.parse(JSON.stringify(originalArray));
Run Code Online (Sandbox Code Playgroud)


Lio*_*rom 15

克隆数组的最快方法

我用这个非常简单的实用函数来测试克隆数组所需的时间.它不是100%可靠,但它可以为您提供克隆现有阵列需要多长时间的大量想法:

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

并测试了不同的方法:

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));
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我尝试对您的答案进行基准测试,结果却大不相同:http://jsben.ch/o5nLG (2认同)
  • @LiorElrom,您的更新不正确,因为方法不可序列化。例如: `JSON.parse(JSON.stringify([function(){}]))` 将输出 `[null]` (2认同)
  • 不错的基准。我在 Mac 上的 2 个浏览器中对此进行了测试:Chrome 版本 81.0.4044.113 和 Safari 版本 13.1 (15609.1.20.111.8),最快的是扩展操作:Chrome 中的 `[...arr]` 和 `4.653076171875ms` Safari 中为“8.565 毫秒”。Chrome 中第二快的是切片函数“arr.slice()”,时间为“6.162109375ms”;Safari 中第二快的是“[].concat(arr)”,时间为“13.018ms”。 (2认同)

Mar*_*n07 8

ECMAScript 2015 方式与Spread操作符:

基本示例:

var copyOfOldArray = [...oldArray]
var twoArraysBecomeOne = [...firstArray, ..seccondArray]
Run Code Online (Sandbox Code Playgroud)

在浏览器控制台中尝试:

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);
Run Code Online (Sandbox Code Playgroud)

参考

  • 请提供一些有关您的论点的链接。 (3认同)

Mar*_*gus 7

看看:链接.这不是速度,而是舒适.除此之外,您可以看到只能在基本类型上使用slice(0).

要创建数组的独立副本而不是refence的副本,可以使用数组切片方法.

例:

要创建数组的独立副本而不是refence的副本,可以使用数组切片方法.

var oldArray = ["mip", "map", "mop"];
var newArray = oldArray.slice();
Run Code Online (Sandbox Code Playgroud)

复制或克隆对象:

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);
Run Code Online (Sandbox Code Playgroud)

来源:链接

  • 如果我正在复制一个对象数组,我希望新数组引用相同的对象而不是克隆对象. (4认同)
  • *原始类型*注释也适用于问题中的“for”循环。 (2认同)

ser*_*inc 7

正如@Dan所说:"这个答案变得过时了.使用基准来检查实际情况",jsperf有一个特定的答案,它没有自己的答案:while:

var i = a.length;
while(i--) { b[i] = a[i]; }
Run Code Online (Sandbox Code Playgroud)

获得960,589次/秒,亚军a.concat()为578,129次/秒,即60%.

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


@aleclarson创造了一个新的,更可靠的基准.


kyn*_*igs 6

这取决于浏览器.如果您查看博客文章Array.prototype.slice与手动数组创建,那么每个程序的性能都有一个粗略的指导:

在此输入图像描述

结果:

在此输入图像描述

  • @diugalde我认为将代码作为图片发布的唯一情况是,代码有潜在危险且不应复制粘贴.在这种情况下,这是非常荒谬的. (2认同)

cie*_*bor 6

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

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

长度检查是必需的,因为Array构造函数在使用一个参数调用时行为不同.

  • 或许比`splice()`更具语义性.但实际上,_apply_和_this_几乎是直观的. (14认同)
  • 但它是最快的吗? (2认同)
  • 你可以使用`Array.of`并忽略长度:`Array.of.apply(Array,array)` (2认同)

mar*_*rds 6

记住.slice()不适用于二维数组.你需要这样的功能:

function copy(array) {
  return array.map(function(arr) {
    return arr.slice();
  });
}
Run Code Online (Sandbox Code Playgroud)

  • 在Javascript中没有二维数组.只有包含数组的数组.你要做的是*深拷贝*,这在问题中是不需要的. (3认同)

Zib*_*bri 6

基准时间!

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());
Run Code Online (Sandbox Code Playgroud)
<textarea rows="50" cols="80" style="font-size: 16; resize:none; border: none;" id="log"></textarea>
Run Code Online (Sandbox Code Playgroud)

自从您单击按钮后,基准测试将运行 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)
Run Code Online (Sandbox Code Playgroud)

火狐(蜘蛛猴引擎):

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)
Run Code Online (Sandbox Code Playgroud)

获胜者代码:

function clone1(arr) {
    return arr.slice(0);
}
Run Code Online (Sandbox Code Playgroud)

获胜引擎:

SpiderMonkey (Mozilla/Firefox)


Gor*_*Gor 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();
Run Code Online (Sandbox Code Playgroud)

如果将original_array的长度设置为1,000,000,则slice方法和concat方法大约需要相同的时间(3-4 ms,具体取决于随机数).

如果将original_array的长度设置为10,000,000,则该slice方法将花费60 ms以上,并且该concat方法需要超过20 ms.