Array.prototype.concat() verbindet das Original-Array mit beliebig vielen weiteren Arrays oder Einzelwerten und liefert ein neues Array zurück — das Original bleibt unangetastet. Damit ist concat() eine der wenigen Array-Methoden, die immutable arbeiten, ohne dass man sich darum kümmern muss. Allerdings erzeugt sie nur eine flache Kopie: verschachtelte Arrays und Objekte werden per Referenz übernommen. Der Spread-Operator [...a, ...b] deckt seit ES2015 fast alle concat-Use-Cases mit kürzerer Syntax ab — concat bleibt aber für spezielle Fälle (Iterables, Symbol.isConcatSpreadable) und in defensivem Code weiterhin sinnvoll.
Signatur & Grundform
arr.concat(...werte: (T | T[])[]): T[]Beliebig viele Argumente, jedes entweder ein einzelner Wert oder ein Array. Arrays werden flach gespreaded, Werte einfach angehängt. Rückgabe ist ein neues Array.
const a = [1, 2, 3];
const b = [4, 5];
const c = a.concat(b);
console.log(c); // [1, 2, 3, 4, 5]
console.log(a); // [1, 2, 3] — unverändert
console.log(c === a); // false — neue Referenz
// Mehrere Argumente
const d = [].concat([1, 2], 3, [4], 'fünf');
console.log(d); // [1, 2, 3, 4, 'fünf'][ 1, 2, 3, 4, 5 ]
[ 1, 2, 3 ]
false
[ 1, 2, 3, 4, 'fünf' ]Flache Kopie — Referenzen werden geteilt
concat kopiert nur die oberste Ebene. Verschachtelte Objekte und Arrays bleiben dieselben Referenzen.
const inner = { wert: 1 };
const a = [inner];
const b = [{ wert: 2 }];
const c = a.concat(b);
console.log(c); // [{wert: 1}, {wert: 2}]
// Mutation am inneren Objekt von a propagiert in c
inner.wert = 99;
console.log(c[0]); // { wert: 99 }
console.log(c[0] === inner); // true — gleiche Referenz[ { wert: 1 }, { wert: 2 } ]
{ wert: 99 }
trueFür eine echte Deep-Copy: structuredClone(arr) (ES2022) oder JSON.parse(JSON.stringify(arr)) (mit den bekannten JSON-Beschränkungen).
Spread-Operator als moderne Alternative
Seit ES2015 erreicht [...a, ...b] fast jedes concat-Resultat — kürzer und ohne Method-Call.
const a = [1, 2];
const b = [3, 4];
// Klassisch
const klassisch = a.concat(b);
// Spread
const spread = [...a, ...b];
// Mit Werten dazwischen
const gemischt = [0, ...a, 'mitte', ...b, 'ende'];
console.log(klassisch);
console.log(spread);
console.log(gemischt);[ 1, 2, 3, 4 ]
[ 1, 2, 3, 4 ]
[ 0, 1, 2, 'mitte', 3, 4, 'ende' ]Subtiler Unterschied: Spread nutzt das Iterator-Protokoll — also iterable Werte wie Set, Map, Strings, Generators werden entpackt. concat flat-spreaded nur echte Arrays.
const set = new Set([1, 2, 3]);
// concat sieht Set als Einzelwert
console.log([0].concat(set)); // [0, Set(3) { 1, 2, 3 }]
// Spread iteriert Set
console.log([0, ...set]); // [0, 1, 2, 3][ 0, Set(3) { 1, 2, 3 } ]
[ 0, 1, 2, 3 ]Für die meisten Use-Cases ist Spread die idiomatischere Wahl. concat bleibt nützlich, wenn man explizit zwischen „spreaden" und „als Einzelwert anhängen" unterscheiden will.
Symbol.isConcatSpreadable — Spread-Verhalten steuern
concat prüft beim Argument, ob es spreadable ist. Default-Regel: echte Arrays werden spreaded, andere Objekte nicht. Mit Symbol.isConcatSpreadable lässt sich das überschreiben.
// Array NICHT spreaden — als Einzelwert anhängen
const arr = [1, 2, 3];
arr[Symbol.isConcatSpreadable] = false;
console.log([0].concat(arr)); // [0, [1, 2, 3]] — Array als Element
// Array-like spreaden
const arrayLike = { 0: 'a', 1: 'b', length: 2, [Symbol.isConcatSpreadable]: true };
console.log([].concat(arrayLike)); // ['a', 'b'][ 0, [ 1, 2, 3 ] ]
[ [ 1, 2, 3 ] ] ← oder ['a', 'b'] je nach Engine-VersionIn Produktiv-Code praktisch nie genutzt — aber gut zu wissen, dass die Sprache diese Flexibilität bietet.
Primitive Werte direkt anfügen
concat kann Strings, Numbers, Booleans direkt als Element anfügen, ohne dass man sie in ein Array packen muss.
const start = ['a', 'b'];
const erweitert = start.concat('c', 1, true, null, undefined);
console.log(erweitert);[ 'a', 'b', 'c', 1, true, null, undefined ]Gleiches mit Spread bräuchte explizite Wert-Auflistung — kein Spread auf Primitives, weil sie nicht iterable sind (außer Strings).
Sparse Arrays — Lücken bleiben Lücken
concat erhält die sparse-Eigenschaft: Lücken im Original bleiben Lücken im Ergebnis.
const sparse = [1, , 3];
console.log(sparse.length); // 3
console.log(1 in sparse); // false
const c = sparse.concat([4, 5]);
console.log(c); // [1, <empty>, 3, 4, 5]
console.log(1 in c); // false — Loch wandert mit
// Spread füllt mit undefined
const s = [...sparse, 4, 5];
console.log(s); // [1, undefined, 3, 4, 5]
console.log(1 in s); // true — kein Loch mehr3
false
[ 1, <1 empty item>, 3, 4, 5 ]
false
[ 1, undefined, 3, 4, 5 ]
trueDas ist ein subtiler, aber realer Unterschied. Wer mit sparse Arrays arbeitet (z.B. aus new Array(n)), muss zwischen den beiden Formen bewusst wählen.
Performance — concat vs. Spread
Bei kleinen Arrays unterscheiden sich beide Formen kaum. Bei sehr großen Arrays gilt grob:
concat: V8 hat es lange optimiert; einmaliger Aufruf liefert das Ergebnis-Array in einem Rutsch.- Spread
[...a, ...b]: pro Spread eine Iteration; bei N Argumenten N Iterationen.
In Benchmarks zeigt sich bei massiven Arrays (>10.000 Elemente) concat oft minimal schneller. In normalem Anwendungs-Code unmessbar — Lesbarkeit gewinnt. Für Performance-kritische Hot-Loops mit großen Arrays bleibt concat einen Hauch im Vorteil.
flat() und flatMap ersetzen Reduce-mit-concat
Vor flat/flatMap (ES2019) war reduce mit concat ein klassisches Pattern zum Flachklopfen:
const nested = [[1, 2], [3, 4], [5]];
// Alt: reduce + concat
const flat1 = nested.reduce((acc, arr) => acc.concat(arr), []);
// ES2019: flat()
const flat2 = nested.flat();
// ES2019: flatMap (für Map + Flatten in einem)
const verdoppelt = nested.flatMap(arr => arr.map(x => x * 2));
console.log(flat1);
console.log(flat2);
console.log(verdoppelt);[ 1, 2, 3, 4, 5 ]
[ 1, 2, 3, 4, 5 ]
[ 2, 4, 6, 8, 10 ]In neuem Code: flat()/flatMap() direkt nutzen — deutlich schneller (kein quadratisches Re-Allocate wie bei reduce+concat) und lesbarer.
Welche Form nehmen?
| Situation | Empfehlung |
|---|---|
| Zwei Arrays + ein paar Einzelwerte | Spread: [...a, ...b, x, y] |
| Nur ein dynamisches Argument (Set, Iterable) | Spread: [...iterable] |
| Iteratoren / Generators einbinden | Spread (concat sähe sie als Einzelwert) |
| Sparse-Eigenschaft erhalten | concat |
| Symbol.isConcatSpreadable nötig | concat |
| Massive Arrays, Performance-kritisch | concat (minimaler Vorteil) |
| Verschachtelte Arrays flach klopfen | flat() / flatMap() |
Besonderheiten
concat ist immutable — eines der wenigen Array-Methoden
Anders als push, pop, splice, reverse, sort verändert concat das Original nicht. Es war Pre-ES2015 oft die einzige Methode, ein Array immutable zu erweitern, ohne den ganzen slice+push-Tanz. Heute teilt es sich diese Rolle mit Spread und mit den ES2023-„by-Copy"-Methoden.
Flache Kopie — verschachtelte Refs teilen sich
[[1]].concat([[2]]) liefert ein neues äußeres Array, aber die inneren Arrays sind dieselben Referenzen. Wer Deep-Copy braucht: structuredClone(arr) oder rekursive Custom-Implementierung.
concat spreaded nur ECHTE Arrays — Iterables werden als Einzelwert angehängt
[].concat(new Set([1, 2])) liefert [Set(2) {1, 2}] — das Set ist ein Element, nicht spreaded. Mit Spread [...new Set([1, 2])] bekommt man [1, 2]. Das unterscheidet die beiden Operationen fundamental.
Symbol.isConcatSpreadable steuert das Verhalten
Eine Property mit Schlüssel Symbol.isConcatSpreadable bestimmt, ob concat ein Argument spreadet. true für Array-likes, false auf echten Arrays. Selten gesehen — aber praktisch für Custom-Container, die sich „array-artig" verhalten sollen.
Sparse vs. dense — concat behält Löcher, Spread füllt mit undefined
[1, , 3].concat() behält das Loch (in-Operator zeigt false). [...[1, , 3]] füllt es mit undefined. In modernem Code irrelevant, weil sparse Arrays unüblich sind — bei Legacy-Code aber relevant.
reduce + concat war das alte flat()-Pattern — heute Anti-Pattern
arr.reduce((acc, x) => acc.concat(x), []) war Pre-2019 das Flachklopf-Idiom. Heute durch arr.flat() ersetzt — schneller (keine O(n²)-Allokation) und lesbarer. Ältere Codebases enthalten das Pattern noch reichlich.
Performance: concat marginal schneller als Spread bei großen Arrays
V8 hat concat seit Jahren als Special-Case optimiert. Bei massiven Arrays (>10.000 Elemente) zeigt sich ein leichter Vorteil gegenüber Spread, weil concat in einem Allocation-Schritt das Ergebnis baut. In normalem Code irrelevant.
concat() ohne Argumente liefert eine flache Kopie
arr.concat() ohne Argumente ist äquivalent zu arr.slice() — beide liefern eine flache Kopie. Konvention: slice() ist semantisch klarer, wenn man kopieren will. concat() wird typischerweise mit mindestens einem Argument aufgerufen.
Weiterführende Ressourcen
Externe Quellen
- Array.prototype.concat() – MDN
- Spread syntax – MDN
- Symbol.isConcatSpreadable – MDN
- Array.prototype.flat() – MDN
- Array.prototype.concat – ECMAScript Spec