在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个回答

8

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

参考资料


可能唯一快速的事情就是输入spread。它比其他方法的性能要低得多。 - XT_Nova
3
请提供一些关于你观点的链接。 - Marian07

7

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

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

在最新的64位Firefox(版本40)中,a.concat()以578,129个操作/秒的成绩位居第二,而本次测试的冠军为960,589个操作/秒,超过了60%。


@aleclarson创建了一个新的、更可靠的基准测试。


1
你应该真正地链接jsperf。你想到的那个已经失效了,因为在每个测试用例中都创建了一个新的数组,除了“while循环”测试。 - aleclarson
1
我制作了一个更准确的新jsperf:https://jsperf.com/clone-array-3 - aleclarson
60% 是什么?比原来快60%? - Peter Mortensen
1
@PeterMortensen:587192是960589的约60%(61.1...)。 - serv-inc

7

测速时间到了!

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)


6

请记住,.slice() 方法无法用于二维数组。你需要使用像这样的函数:

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

4
在JavaScript中,没有二维数组,只有包含数组的数组。你正在尝试做的是深拷贝,但在这个问题中并不需要。 - Aloso

6
有一个更为简洁的解决方案:
var srcArray = [1, 2, 3];
var clonedArray = srcArray.length === 1 ? [srcArray[0]] : Array.apply(this, srcArray);

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


2
但它是最快的吗? - Chris Wesseling
15
或许比 splice() 更语义化,但实际上,apply()this 并不直观。 - Michael Piefel
在Chrome上表现最慢- http://jsperf.com/new-array-vs-splice-vs-slice/113 - chrismarx
3
你可以使用Array.of并忽略长度:Array.of.apply(Array, array) - Oriol

6

1
"arguments" 不是一个合适的数组,他正在使用 "call" 强制让 "slice" 在集合上运行。结果可能会误导。 - lincolnk
是的,我本来想在我的帖子中提到这一点的,现在随着浏览器的改进,这些统计数据可能会发生变化,但它们可以给出一个大致的概念。 - kyndigs
2
@diugalde 我认为唯一可以将代码发布为图片的情况是当代码可能具有潜在危险性,不应该被复制粘贴。但在这种情况下,这样做相当荒谬。 - Florian Wendelborn

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毫秒。


a5中,dup.push是错误的。应该使用dup[i] = - 4esn0k

4
在ES6中,你可以简单地利用展开语法
示例:
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']

3

有几种方法可以复制一个数组。基本上,复制分为两种方式:

  1. 浅拷贝
  2. 深拷贝

浅拷贝仅仅覆盖了数组的第一层,其余部分都是引用。如果需要复制嵌套元素的真实副本,则需要进行深层克隆。

例如:

const arr1 = [1,2,3,4,5,6,7]           
// Normal Array (shallow copy is enough)     
const arr2 = [1,2,3,[4],[[5]],6,7]          
// Nested Array  (Deep copy required) 


Approach 1 : Using (...)Spread Operator  (Shallow copy enough)
const newArray = [...arr1] // [1,2,3,4,5,6,7]

Approach 2 : Using Array builtIn Slice method (Deep copy)  
const newArray = arr1.slice()  // [1,2,3,4,5,6,7]

Approach 3 : Using Array builtIn Concat method (Deep a copy)
const newArray = [].concat(arr1)  // [1,2,3,4,5,6,7]

Approach 4 : Using JSON.stringify/parse. (Deep a copy & fastest)
const newArray = JSON.parse(JSON.stringify(arr2));)  // [1,2,3,[4],[[5]],6,7]

Approach 5: Using own recursive function or using loadash's __.cloneDeep method. (Deep copy)

3
        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 M P

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