javascript - operatorul spread vs array.concat()


Care este diferența dintre operatorul spread și array.concat()

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

Funcția Array.concat()

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

Ambele rezultate sunt aceleași. Deci, în ce fel de scenarii vrem să le folosim? Și care este cel mai bun pentru performanță?

Răspuns #1

concat și operatorii de răspândire sunt foarte diferiți atunci când argumentul nu este un array.

Când argumentul nu este un array, concat îl adaugă ca întreg, în timp ce ... încearcă să-l itereze și eșuează dacă nu reușește. Luați în considerare:

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

Aici, concat tratează șirul atomic, în timp ce ... folosește iteratorul său implicit, caracter cu caracter.

Un alt exemplu:

x = 99;

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

Din nou, pentru concat numărul este un atom, ... încearcă să-l itereze și eșuează.

În sfârșit :

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 nu încearcă să itereze generatorul și îl adaugă ca întreg, în timp ce ... preia frumos toate valorile din el.

Pe scurt, când argumentele tale sunt posibil non-tablouri, alegerea între concat și ... depinde de faptul dacă vrei ca acestea să fie iterate.

Cele de mai sus descriu comportamentul implicit al concat, totuși, ES6 oferă o modalitate de a-l suprascrie cu Symbol.isConcatSpreadable. Implicit, acest simbol este true pentru array-uri și false pentru orice altceva. Setarea lui la true spune concat să itereze argumentul, exact cum face ...:

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

Din punct de vedere al performanței, concat este mai rapid, probabil pentru că poate beneficia de optimizări specifice array-urilor, în timp ce ... trebuie să respecte protocolul comun de iterare. Timpuri:

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

Răspunsul #2

.concat() este mai rapid, în rest funcționează la fel presupunând că ambele argumente sunt liste. Iată timpul necesar pentru a fuziona două array-uri cu câte 10 milioane de elemente fiecare (valoarea mai mică este mai bună):

Browser[...a, ...b]a.concat(b)
Chrome 113350ms30ms
Firefox 113400ms63ms
Safari 16.492ms71ms

Am rulat acest cod pe un MacBook Air M1 cu 8GB de RAM:

const arraySize = 10000000;
const trials = 50;

const array1 = [];
const array2 = [];
for (let i = 0; i < arraySize; ++i) {
  array1.push(i);
  array2.push(i);
}

let spreadTime = 0;
for (let i = 0; i < trials; ++i) {
  const start = performance.now();
  const array3 = [...array1, ...array2];
  const end = performance.now();

  spreadTime += end - start;
}

let concatTime = 0;
for (let i = 0; i < trials; ++i) {
  const start = performance.now();
  const array3 = array1.concat(array2);
  const end = performance.now();

  concatTime += end - start;
}

// performance.now() returns milliseconds with a
// 5 microsecond resolution in isolated contexts and a
// 100 microsecond resolution in non-isolated contexts.
spreadTime = Math.round(spreadTime / trials * 1000) / 1000;
concatTime = Math.round(concatTime / trials * 1000) / 1000;
console.log(`${arraySize} items - spread: ${spreadTime}ms concat: ${concatTime}ms`);

Răspunsul #3

Ei bine, console.log(['one', 'two', 'three', 'four', 'five']) are același rezultat, așa că de ce să folosești oricare dintre ele aici? :P

În general, ai folosi concat atunci când ai două (sau mai multe) array-uri din surse arbitrare, iar ai folosi sintaxa cu răspândire în literalul array-ului dacă elementele suplimentare care fac întotdeauna parte din array sunt cunoscute dinainte. Așadar, dacă ai avea un literal de array cu concat în codul tău, folosește direct sintaxa cu răspândire și folosește concat în celelalte cazuri:

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

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

De asemenea, cele două alternative se comportă destul de diferit atunci când au de-a face cu valori care nu sunt array-uri.

Răspunsul #4

Singura diferență pe care o consider validă este că utilizarea operatorului spread pentru un array de dimensiune mare îți va da eroarea Maximum call stack size exceeded, pe care o poți evita folosind operatorul 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)

Răspunsul #5

Actualizare:

Concat este acum întotdeauna mai rapid decât spread. Benchmark-ul următor arată că sunt combinate atât array-uri de dimensiuni mici, cât și mari: https://jsbench.me/nyla6xchf4/1

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

Anterior:

Deși unele dintre răspunsuri sunt corecte în ceea ce privește performanța pe matrice mari, performanța este destul de diferită atunci când lucrați cu matrice mici.

Îți poți verifica rezultatele singur la https://jsperf.com/spread-vs-concat-size-agnostic.

După cum puteți vedea, spread este cu 50% mai rapid pentru array-urile mai mici, în timp ce concat este de mai multe ori mai rapid pentru array-urile mari.