оператор распространения vs array.concat()

В чем разница между spread operator и array.concat()

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

Ответ 1

Well console.log(['one', 'two', 'three', 'four', 'five']) имеет такой же результат, поэтому зачем использовать здесь? :П

В общем случае вы использовали бы concat когда у вас есть два (или более) массива из произвольных источников, и вы бы использовали синтаксис распространения в литерале массива, если раньше были известны дополнительные элементы, которые всегда являются частью массива. Поэтому, если у вас будет литерал массива с concat в вашем коде, просто используйте синтаксис распространения и просто используйте concat иначе:

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

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

Кроме того, две альтернативы ведут себя по-разному, когда имеют дело с значениями, отличными от массива.

Ответ 2

Как сказал @Bergi, 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');

Ответ 3

Единственное различие, которое я считаю правдивым, заключается в том, что использование оператора с расширенными размерами большого массива даст вам 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)

Ответ 4

Я отвечаю только на вопрос производительности, так как уже есть хорошие ответы относительно сценариев. Я написал тест и выполнил его в самых последних браузерах. Ниже приведены результаты и код.

/*
 * 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);

Я также написал о теме в своем блоге: https://www.malgol.com/how-to-merge-two-arrays-in-javascript/.