【问题标题】:Difference between using a spread syntax (...) and push.apply, when dealing with arrays处理数组时使用扩展语法 (...) 和 push.apply 之间的区别
【发布时间】:2025-12-19 10:05:07
【问题描述】:

我有两个数组,

const pets = ["dog", "cat", "hamster"]

const wishlist = ["bird", "snake"]

我想将wishlist追加到pets,可以使用两种方法完成,

方法一:

pets.push.apply(pets,wishlist)

结果:[ 'dog', 'cat', 'hamster', 'bird', 'snake' ]

方法二:

pets.push(...wishlist)

这也会导致:[ 'dog', 'cat', 'hamster', 'bird', 'snake' ]

当我处理更大的数据时,这两种方法在性能方面有区别吗?

【问题讨论】:

  • 定义“更大”数据 - 使用方法 2 时 firefox 看起来更慢...其他浏览器可能更快...尝试您自己的基准测试
  • 如果你要转译成 ES5,很有可能 Babel/TypeScript 等会生成与 pets.push.apply 完全相同的代码。在任何情况下,影响应用程序速度的任何性能差异的可能性都是微乎其微的。你为什么想知道?
  • 在 JS 中,您可以向函数发送不定数量的参数,但不是无限的。根据当前会话可用的堆栈大小,有一个限制,例如最大 150-300K 参数。根据您的问题,当我进行基准测试时,push.apply 似乎工作得更快。
  • 如果不需要保留对数组的引用.concat()会更合适。
  • 您要求的是实现细节,即响应因浏览器和时间而异(因为实现细节可能会发生变化)。从概念上讲,这两种技术是相同的。

标签: javascript arrays ecmascript-6 apply spread-syntax


【解决方案1】:

user6445533 的答案被接受为答案,但我觉得测试用例有点奇怪。这似乎不像您通常应该如何使用传播运算符。

为什么不能像这样:

let newPets = [...pets, ...wishlist]

它不会遇到所描述的任何 * 问题。 就像 Hashbrown 提到的那样,它也可以为您带来性能优势。

*我也在学习 ES6。对不起,如果我错了。

【讨论】:

    【解决方案2】:

    使用 push 可以追加到现有数组,使用扩展运算符创建副本。

    a=[1,2,3]
    b=a
    a=[...a, 4]
    alert(b);
    

    => 1、2、3

     a=[1,2,3]
    b=a
    a.push(4)
    alert(b);
    

    => 1、2、3、4

    push.apply 也是如此:

    a=[1,2,3]
    c=[4]
    b=a
    Array.prototype.push.apply(a,c)
    alert(b);
    

    => 1、2、3、4

    concat 是一个副本

    a=[1,2,3]
    c=[4]
    b=a
    a=a.concat(c)
    alert(b);
    

    => 1、2、3

    通过引用更可取,尤其是对于较大的数组。

    Spread 运算符是一种快速的复制方式,传统上可以通过以下方式完成:

    a=[1,2,3]
    b=[]
    a.forEach(i=>b.push(i))
    a.push(4)
    alert(b);
    

    => 1、2、3

    如果您需要一个副本,请使用扩展运算符,这样做很快。或者使用@ftor 指出的concat。如果没有,请使用推送。但是请记住,在某些情况下您不能进行变异。此外,使用这些函数中的任何一个,您都将获得浅拷贝,而不是深拷贝。对于深拷贝,您将需要 lodash。在这里阅读更多:https://slemgrim.com/mutate-or-not-to-mutate/

    【讨论】:

      【解决方案3】:

      用于附加到大数组 spread operator is vastly faster。我不知道@ftor / @Liau Jian Jie 是如何得出他们的结论的,可能是不好的测试。

      Chrome 71.0.3578.80 (Official Build) (64-bit), FF 63.0.3 (64-bit), & Edge 42.17134.1.0

      这是有道理的,因为concat() 复制了数组,甚至不尝试使用相同的内存。

      关于“突变”的事情似乎没有任何依据;如果您要覆盖旧数组,concat() 没有任何好处。

      不使用... 的唯一原因是堆栈溢出,我同意您不能使用...apply的其他答案。
      但即便如此,在所有浏览器中仅使用 for {push()} 或多或少速度是 concat() 的两倍,并且不会溢出
      没有理由使用concat(),除非您需要保留旧数组。

      【讨论】:

        【解决方案4】:

        将问题解释为which is more performant in general, using .push() as an example,看起来 apply [只是稍微] 快了(MS Edge 除外,见下文)。

        Here's a performance test 仅介绍为这两种方法动态调用函数的开销。

        function test() { console.log(arguments[arguments.length - 1]); }
        var using = (new Array(200)).fill(null).map((e, i) => (i));
        

        test(...using);
        

        test.apply(null, using)
        

        我在Chrome 71.0.3578.80 (Official Build) (64-bit)FF 63.0.3 (64-bit)Edge 42.17134.1.0 中进行了测试,这些是我自己运行几次后的结果 最初的结果总是有偏差方式或其他方式

        正如您所见,Edge 似乎对 apply 的实现比 it... 的实现更好(但不要尝试跨浏览器比较结果,我们无法判断 Edge 是否apply 比其他的更好,... 更差,或者两者兼而有之)。
        鉴于此,除非您专门针对 Edge,否则我会说使用 ...,因为它看起来更清晰,特别是如果您需要将对象传递回 apply 以获取 this

        它也可能取决于数组的大小,所以就像@Jaromanda X 说的那样,做你自己的测试并更改200,如果你真的需要确定的话。


        其他答案将问题解释为which would be better for .push() specifically,并赶上正在解决的“问题”,只是推荐给just use .concat(),这基本上是标准why are you doing it that way?,可能会惹恼一些人来自 google,他们不寻找与 .push() 相关的解决方案(例如,Math.max,或您自己的自定义函数)。

        【讨论】:

          【解决方案5】:

          除了what ftor pointed outArray.prototype.concat 的平均速度至少比数组扩展运算符快 1.4 倍。

          在此处查看结果: https://jsperf.com/es6-add-element-to-create-new-array-concat-vs-spread-op

          您可以在自己的浏览器和机器上运行测试:https://www.measurethat.net/Benchmarks/Show/579/1/arrayprototypeconcat-vs-spread-operator

          【讨论】:

            【解决方案6】:

            Function.prototype.apply 和扩展语法在应用于大型数组时都可能导致堆栈溢出:

            let xs = new Array(500000),
             ys = [], zs;
            
            xs.fill("foo");
            
            try {
              ys.push.apply(ys, xs);
            } catch (e) {
              console.log("apply:", e.message)
            }
            
            try {
              ys.push(...xs);
            } catch (e) {
              console.log("spread:", e.message)
            }
            
            zs = ys.concat(xs);
            console.log("concat:", zs.length)

            请改用Array.prototype.concat。除了避免堆栈溢出concat 还具有避免突变的优点。突变被认为是有害的,因为它们会导致微妙的副作用。

            但这不是教条。如果您在函数范围内并执行突变以提高性能并减轻垃圾收集,您可以执行突变,只要它们在父范围中不可见。

            【讨论】:

              【解决方案7】:

              如果您使用的是 ES2015,那么扩展运算符是可行的方法。与其他方法相比,使用扩展运算符,您的代码看起来不那么冗长且更简洁。谈到速度,我相信这两种方法几乎没有选择余地。

              【讨论】:

              • 请提供真实的测试来证明您的速度要求。在我的测试中,传播操作比Function.prototype.apply 慢得多。如gist.github.com/joliss/4de65fa03d547fc1c814
              • 无论速度如何,有时您都不想改变数组。在这些情况下,在处理现代 JavaScript 或打字稿时,传播总是最好的。例如,在使用 redux reducer 时。与 _array.push() 进行比较时,我特别在谈论。 concat 始终可用且性能良好,但看起来不如预期的好。