展开运算符 vs array.concat()

IT技术 javascript arrays typescript ecmascript-6 operators
2021-03-16 12:57:47

是有什么区别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));

两个结果是一样的。那么,我们想在什么样的场景中使用它们呢?哪一种最适合性能?

6个回答

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数字是一个原子,...尝试迭代它并失败。

最后:

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不尝试迭代生成器并将其作为一个整体附加,同时...很好地从中获取所有值。

总而言之,当您的参数可能是非数组时,在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' ]

性能方面concat更快,可能是因为它可以从特定于数组的优化中受益,同时...必须符合通用迭代协议。时间:

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');

这应该是正确答案。比 bergi 的回答更不主观。
2021-05-18 12:57:47

Wellconsole.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 :-)

在处理非数组值时,这两种替代方法的行为也大不相同。

@DrazenBjelovuk.concat(x)让读者假设它x也是一个数组。当然,concat也可以处理非数组值,但这不是它的主要操作模式。特别是如果x是任意(未知)值,您需要编写.concat([x])以确保它始终按预期工作。无论如何,只要您必须编写数组文字,我就会说您应该只使用扩展语法而不是concat.
2021-04-27 12:57:47
@RameshRajendran 在其他两个数组之间,当然。“在这两者之间其他阵列”,不知道你的意思。
2021-05-09 12:57:47
FWIW,有一个可衡量的性能差异。参见jsperf.com/spread-vs-concat-vs-push
2021-05-11 12:57:47
@RameshRajendran 相当于['one'].concat(parts, ['two', 'three'])(或者['one'].concat(parts).concat(['two', 'three'])如果您不想传递多个参数)
2021-05-13 12:57:47
是否可以在另一个数组之间进行连接或传播数组?
2021-05-15 12:57:47

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

/*
 * 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);
谢谢,就实际问题而言,这实际上是一个有用的答案。
2021-04-27 12:57:47
我很少有 10.000.000 个元素。宁愿/也希望看到合并 10、100 或 1000 个元素并多次合并的比较。
2021-05-07 12:57:47

我认为有效的一个区别是,对大数组大小使用扩展运算符会给您带来错误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)

我知道,OP 没有push这个元素。但是我们通常push使用数组中的元素,所以我试图展示当我们使用带有传播的推送时的后果。
2021-04-21 12:57:47
这与问题及其误导性无关
2021-05-07 12:57:47
您应该澄清当函数调用在内部使用传播时会使用堆栈。然而,当它只是一个数组文字并且使用了扩展时,就不会使用任何堆栈,因此不会发生最大调用堆栈。
2021-05-08 12:57:47
这种传播语法(函数调用)的使用不是所问的(数组文字)。
2021-05-17 12:57:47

虽然有些回答在大阵列上的性能方面是正确的,但在处理小阵列时,性能却大不相同。

您可以在https://jsperf.com/spread-vs-concat-size-agnostic 上自行检查结果

如您所见,spread较小阵列的速度提高了 50%,而concat大型阵列的速度提高了数倍。

链接已断开——因此,最好提供链接内容的摘要,以防链接断开。
2021-05-13 12:57:47