32

我有两个数组,

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' ]

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

4

7 回答 7

28

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,它还具有避免突变的优点。突变被认为是有害的,因为它们会导致微妙的副作用。

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

于 2016-09-27T08:10:26.483 回答
2

除了ftor 指出的,Array.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

于 2017-10-24T05:44:43.457 回答
2

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

这是针对两种方法动态调用函数的开销的性能测试

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更好的实现...(但不要试图比较跨浏览器的结果,我们无法判断 Edge 是否apply比其他的更好,更差...,或者两者兼而有之这个数据)。
鉴于此,除非您专门针对 Edge,否则我会说它...看起来更清晰,特别是如果您需要将对象传递回applyfor this

它也可能取决于数组的大小,所以就像说的那样,如果您需要真正确定@Jaromanda X,请进行自己的测试和更改。200


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

于 2018-12-08T03:21:46.677 回答
1

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

通过引用是优选的,特别是对于较大的阵列。

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

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/

于 2019-02-02T00:55:27.650 回答
1

对于附加到大型数组,展开运算符要快得多。我不知道@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()则没有任何好处。

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

于 2018-12-09T05:55:19.897 回答
1

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

为什么不能这样做:

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

如前所述,它不会面临任何 stackoverflow 问题。就像 Hashbrown 提到的那样,它也可以为您带来性能优势。

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

于 2020-04-05T13:29:47.387 回答
-2

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

于 2016-09-27T05:22:22.120 回答