JavaScript中最快的将数组长度翻倍的方法是什么?

4
[1,2,3,4,5].duplicate(); // [1,2,3,4,5,1,2,3,4,5]

也许可以这样做:
var array = [1,2,3,4,5];
array.push(array);

但是什么是最快的方法?

7
你不仅仅是想“加倍长度”,对吧?你似乎想要复制数组的内容。 - Pointy
@Pointy 是的,没错。 - j_d
当我有两种实现同一目的的方法时,我会使用 https://jsperf.com/ 来运行我的测试。它是一个很好的基准测试工具。 - Corey Young
3个回答

9
你可以使用 concat 方法并用新数组替换原始数组:
array = array.concat(array);

或者使用pushapply(修改原始数组):

[].push.apply(array, array);

或者使用 push展开运算符(修改原始数组):

array.push(...array);

concat方法似乎无法正常工作(至少在Chrome开发工具中)。 - j_d
在Chrome开发者工具中,var array = [1,2,3]; array = array.concat(array); 返回 [1, 2, 3, 1, 2, 3] - Oriol
@Oriol,你不需要再将concat结果分配给数组,只需执行array.concat(array);即可完成与array = array.concat(array);相同的操作。 - Magicprog.fr
这不会修改原始内容。覆盖它也不会。 - Lye Fish
2
@Magicprog.fr 但是concat不会改变原始数组,所以我用新的数组替换它。push会修改原始数组。 - Oriol
@Oriol 好的,谢谢你解释这个,我之前不知道。 - Magicprog.fr

3

在JS中,最快的做某件事情的方法通常是使用类似于C语言的本地语句。

我认为这将是最快的方法:

function duplicate(arr) {
      for(var i=0,len=arr.length;i<len;i++)
            arr[i+len]=arr[i];
}

一个更加优雅但速度较慢的方法如下:
arr=arr.concat(arr);

这是另一个例子:
[].push.apply(arr,arr);

EcmaScript 6 还允许您使用展开运算符来完成同样的操作。该运算符会将数组中的所有值放置在您编写的位置,因此这个代码 var arr = [0,1,2,3];console.log(...arr) 可以转换成var arr = [0,1,2,3];console.log(0,1,2,3)

arr.push(...arr);

然而,EcmaScript 6并没有被广泛支持(包括这个特性)。所以如果我是你,我不会使用它。尽管ES 6刚刚被批准和发布(正如@LyeFish在评论中所说),但大多数浏览器仍在努力适应它。

EcmaScript-262第6版于5天前正式发布(感谢@LyeFish)! http://www.ecma-international.org/news/index.html


@vinayakj:所有这些答案都使用本地JavaScript。 - Lye Fish
@vinayakj 对的。虽然它们在大多数情况下的性能非常相似,但是根据实现和数组长度的不同,第一个可能是最快的。 - BrainOverflow
2
ES6的笔记很不错。自从规范已经定稿并被接受以来,ES6功能不再是实验性的。https://esdiscuss.org/topic/ecmascript-2015-is-now-an-ecma-standard - Lye Fish

0
根据我的测试结果,胜者是concat方法和push方法。我还测试了循环方法和我自己新引入的splice例程。
这是所使用的测试“基础架构”代码:
var toArray = Function.prototype.call.bind(Array.prototype.slice);

Function.prototype.testerTimes = function(number) {
    var func = this;
    return function() {
        var args = toArray(arguments), i = 0;
        for( ; i < number; func.apply(this, args), i++);
    };
};

Function.prototype.testerTime = function(units) {
    var func = this;
    return function() {
        var start = Date.now(), diff;
        func.apply(this, toArray(arguments));
        diff = Date.now() - start;
        return units === "s" ? diff / 1000 : units === "m" ? diff / 60000 : units === "h" ? diff / 3600000 : diff;
    };
};

Function.prototype.testerToConsole = function(prefix, message) {
    var func = this;
    return function() {
        console.log(prefix + message + func.apply(this, toArray(arguments)));    
    };
};

Function.prototype.makeTestReady = function(times, units, prefix, message) {
    return this.testerTimes(times).testerTime(units).testerToConsole(prefix, message);
};

这是测试代码:
function genArray(num) {
    for(var i = 0, arr = []; i < num; arr.push(++i));
    return arr;
};

var numberOfRuns = 1000000;
var timeUnit = "s";
var messagePrefix = "   ";

var funcs = [
    function duplicateConcat(arr) {
        var arrCopy = arr.slice(0);
        return arrCopy.concat(arrCopy);    
    },

    function duplicatePush(arr) {
        var arrCopy = arr.slice(0);
        arrCopy.push.apply(arrCopy, arrCopy);
        return arrCopy;
    },

    function duplicateLoop(arr) {
        var arrCopy = arr.slice(0);
        for(var i = 0, len = arrCopy.length; i < len; i++) {
            arrCopy[len + i] = arrCopy[i]; 
        }    
        return arrCopy;
    },

    function duplicateSplice(arr) {
        var arrCopy = arr.slice(0);
        arrCopy.splice.apply(arrCopy, [arrCopy.length, 0].concat(arrCopy));
        return arrCopy;
    }
].map(function(func, index, arr) {
    return func.makeTestReady(numberOfRuns, timeUnit, messagePrefix, func.name + ": ");
});

for(var i = 5; i < 25; i+= 5) {
    console.log(i + "-element array:");
    funcs.forEach(function(func) {
        func(genArray(i));
    });
}

这里是每个函数在数组大小为5、10、15和20的情况下运行100万次的结果:

5-element array: 
   duplicateConcat: 0.236  
   duplicatePush: 0.228 
   duplicateLoop: 0.372 
   duplicateSplice: 0.45
10-element array: 
   duplicateConcat: 0.241  
   duplicatePush: 0.273    
   duplicateLoop: 0.433 
   duplicateSplice: 0.48  
15-element array:
   duplicateConcat: 0.261 
   duplicatePush: 0.293
   duplicateLoop: 0.5
   duplicateSplice: 0.522 
20-element array: 
   duplicateConcat: 0.24 
   duplicatePush: 0.311
   duplicateLoop: 0.602 
   duplicateSplice: 0.558

提醒一下,使用.apply()时,执行toArray(arguments)是不必要的。自ECMAScript 3以来,.apply()方法已被指定为接受本地数组和参数对象。ECMAScript 5随后扩展了其定义。话虽如此...比较.concat().push.apply似乎有点毫无意义,因为它们执行两个不同的操作。没有人会执行array.slice(0),然后再执行.push.apply,因为单个.concat()将具有相同的效果。 - user1106925
toArray() 函数是基于向后兼容的目的使用的,以防某些人使用旧版本浏览器或旧版本的 node。在 push 版本中,arr.slice(0) 语句是必要的,因为否则数组会不断增长并且你会溢出堆栈。arr.slice(0) 语句被添加到每个函数中,以确保它们等效,除了它们自己独特的复制实现。这是为了确保性能指标没有偏见而做的。 - DRD
是的,我理解你的设置需要 arr.slice(0)。我只是想说,即使没有它,比较这两种方法也是在比较两个不同的东西(而不是对同一件事情的不同方法)。至于旧浏览器,你需要使用类似 IE5 的浏览器才会出现兼容性问题。如果 SO 甚至能够渲染,我会感到惊讶。 - user1106925
我不确定我是否理解您的意思。是的,它们是不同的方法。concat返回一个新数组,而push将元素添加到现有数组中。哪种方法更有效地将一个数组复制到“自身”?时间方面,连接略快一些。 - DRD
重点是,如果需要改变原始值,就不应该使用.concat(),因此它的性能优势就不再相关。而如果不想改变原始值,那么就永远不会使用.push.apply() - user1106925
当然可以。我的帖子只是关注于那些接受一个数组,复制它,使用不同的方法修改副本以达到相同结果并返回副本的函数的性能。开发人员如何使用这些信息超出了我的条目范围。 - DRD

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