扩展运算符与 array.concat()

spread operatorarray.concat()的区别是什么

let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log([...numbers, ...parts]);

Array.concat()功能

let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log(numbers.concat(parts));

两个结果是一样的。那么,我们需要用到什么样的场景呢?那么哪一个对性能最好呢?

68619 次浏览

那么 console.log(['one', 'two', 'three', 'four', 'five'])也有相同的结果,那么为什么要在这里使用呢? : P

一般来说,当您有两个(或更多)来自任意源的数组时,您会使用 concat,并且如果以前就知道作为数组的一部分的其他元素,则会使用数组文字中的扩展语法。因此,如果代码中有一个包含 concat的数组文字,那么只需使用扩展语法,否则只需使用 concat:

[...a, ...b] // bad :-(
a.concat(b) // good :-)


[x, y].concat(a) // bad :-(
[x, y, ...a]    // good :-)

另外,当处理非数组值时,这两个替代方案的行为也完全不同。

我认为有效的一个区别是,对于大数组大小使用扩展运算符会给出 Maximum call stack size exceeded的错误,而使用 concat运算符可以避免这个错误。

var  someArray = new Array(600000);
var newArray = [];
var tempArray = [];




someArray.fill("foo");


try {
newArray.push(...someArray);
} catch (e) {
console.log("Using spread operator:", e.message)
}


tempArray = newArray.concat(someArray);
console.log("Using concat function:", tempArray.length)

当参数不是数组时,concat和传播非常不同。

当参数不是数组时,concat将它作为一个整体添加,而 ...尝试迭代它,如果不能,则会失败。考虑一下:

a = [1, 2, 3]
x = 'hello';


console.log(a.concat(x));  // [ 1, 2, 3, 'hello' ]
console.log([...a, ...x]); // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]

在这里,concat自动处理字符串,而 ...使用其默认迭代器 char-by-char。

另一个例子:

x = 99;


console.log(a.concat(x));   // [1, 2, 3, 99]
console.log([...a, ...x]);  // TypeError: x is not iterable

同样,对于 concat,这个数字是一个原子,...尝试迭代它,但是失败了。

Finally:

function* gen() { yield *'abc' }


console.log(a.concat(gen()));   // [ 1, 2, 3, Object [Generator] {} ]
console.log([...a, ...gen()]);  // [ 1, 2, 3, 'a', 'b', 'c' ]

concat makes no attempt to iterate the generator and appends it as a whole, while ... nicely fetches all values from it.

总而言之,当参数可能是非数组时,concat...之间的选择取决于您是否希望迭代它们。

上面描述了 concat的默认行为,然而,ES6提供了一种方式Symbol.isConcatSpreadable覆盖它。默认情况下,该符号是数组的 true,其他所有符号的 false。将其设置为 true会告诉 concat迭代参数,就像 ...所做的那样:

str = 'hello'
console.log([1,2,3].concat(str)) // [1,2,3, 'hello']


str = new String('hello');
str[Symbol.isConcatSpreadable] = true;
console.log([1,2,3].concat(str)) // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]

Performance-wise concat is faster, probably because it can benefit from array-specific optimizations, while ... has to conform to the common iteration protocol. Timings:

let big = (new Array(1e5)).fill(99);
let i, x;


console.time('concat-big');
for(i = 0; i < 1e2; i++) x = [].concat(big)
console.timeEnd('concat-big');


console.time('spread-big');
for(i = 0; i < 1e2; i++) x = [...big]
console.timeEnd('spread-big');




let a = (new Array(1e3)).fill(99);
let b = (new Array(1e3)).fill(99);
let c = (new Array(1e3)).fill(99);
let d = (new Array(1e3)).fill(99);


console.time('concat-many');
for(i = 0; i < 1e2; i++) x = [1,2,3].concat(a, b, c, d)
console.timeEnd('concat-many');


console.time('spread-many');
for(i = 0; i < 1e2; i++) x = [1,2,3, ...a, ...b, ...c, ...d]
console.timeEnd('spread-many');

我只是回答性能问题,因为已经有关于场景的好的答案。我编写了一个测试,并在最新的浏览器上执行它。下面是结果和代码。

/*
* Performance results.
* Browser           Spread syntax      concat method
* --------------------------------------------------
* Chrome 75         626.43ms           235.13ms
* Firefox 68        928.40ms           821.30ms
* Safari 12         165.44ms           152.04ms
* Edge 18           1784.72ms          703.41ms
* Opera 62          590.10ms           213.45ms
* --------------------------------------------------
*/

在我编写和使用的代码下面。

const array1 = [];
const array2 = [];
const mergeCount = 50;
let spreadTime = 0;
let concatTime = 0;


// Used to popolate the arrays to merge with 10.000.000 elements.
for (let i = 0; i < 10000000; ++i) {
array1.push(i);
array2.push(i);
}


// The spread syntax performance test.
for (let i = 0; i < mergeCount; ++i) {
const startTime = performance.now();
const array3 = [ ...array1, ...array2 ];


spreadTime += performance.now() - startTime;
}


// The concat performance test.
for (let i = 0; i < mergeCount; ++i) {
const startTime = performance.now();
const array3 = array1.concat(array2);


concatTime += performance.now() - startTime;
}


console.log(spreadTime / mergeCount);
console.log(concatTime / mergeCount);

更新 :

Concat 现在总是比 spread快。下面的基准测试显示了小型和大型数组的连接情况: https://jsbench.me/nyla6xchf4/1

enter image description here

// preparation
const a = Array.from({length: 1000}).map((_, i)=>`${i}`);
const b = Array.from({length: 2000}).map((_, i)=>`${i}`);
const aSmall = ['a', 'b', 'c', 'd'];
const bSmall = ['e', 'f', 'g', 'h', 'i'];


const c = [...a, ...b];
// vs
const c = a.concat(b);


const c = [...aSmall, ...bSmall];
// vs
const c = aSmall.concat(bSmall)

上一页:

虽然在大型数组的性能方面,有些答复是正确的,但是在处理小型数组时,性能就大不相同了。

You can check the results for yourself at https://jsperf.com/spread-vs-concat-size-agnostic.

正如您所看到的,对于较小的数组,扩展速度要快50% ,而对于较大的数组,concat要快几倍。

There is one very important difference between concat and push in that the former does not mutate the underlying array, requiring you to assign the result to the same or different array:

let things = ['a', 'b', 'c'];
let moreThings = ['d', 'e'];
things.concat(moreThings);
console.log(things); // [ 'a', 'b', 'c' ]
things.push(...moreThings);
console.log(things); // [ 'a', 'b', 'c', 'd', 'e' ]

我已经看到了由于假设 concat改变了数组(代表朋友交谈;)而导致的错误。

@ georg 的回答有助于我们看到这种比较。我也很好奇。单位()将比较在运行中,它是迄今为止最差的。别用。如果速度是优先考虑的话。(有些事我直到现在才知道)

  let big = new Array(1e5).fill(99);
let i, x;


console.time("concat-big");
for (i = 0; i < 1e2; i++) x = [].concat(big);
console.timeEnd("concat-big");


console.time("spread-big");
for (i = 0; i < 1e2; i++) x = [...big];
console.timeEnd("spread-big");


console.time("flat-big");
for (i = 0; i < 1e2; i++) x = [[], big].flat();
console.timeEnd("flat-big");


let a = new Array(1e3).fill(99);
let b = new Array(1e3).fill(99);
let c = new Array(1e3).fill(99);
let d = new Array(1e3).fill(99);


console.time("concat-many");
for (i = 0; i < 1e2; i++) x = [1, 2, 3].concat(a, b, c, d);
console.timeEnd("concat-many");


console.time("spread-many");
for (i = 0; i < 1e2; i++) x = [1, 2, 3, ...a, ...b, ...c, ...d];
console.timeEnd("spread-many");


console.time("flat-many");
for (i = 0; i < 1e2; i++) x = [1, 2, 3, a, b, c, d].flat();
console.timeEnd("flat-many");