ES2015 brachte drei zusammengehörige Verbesserungen für Funktions-Parameter: Default-Werte ersetzen das alte x = x || 5-Muster, Rest-Parameter ersetzen das arguments-Objekt, Spread im Aufruf ersetzt fn.apply(null, args). Alle drei nutzen die ...-Syntax — Rest in der Parameter-Liste, Spread im Aufruf. Dieser Artikel zeigt die Mechanik jeder Form mit Code, dokumentiert die Eigenheiten (Default-Evaluierung pro Aufruf, Rest muss letzter Parameter sein) und kombiniert die drei in praktischen Mustern.
Default-Parameter — Werte für fehlende Argumente
Hinter = steht der Default-Wert, der greift, wenn das Argument fehlt oder explizit undefined ist.
function gruss(name = 'Gast', sprache = 'de') {
return sprache === 'de' ? `Hallo, ${name}!` : `Hello, ${name}!`;
}
console.log(gruss()); // 'Hallo, Gast!'
console.log(gruss('Anna')); // 'Hallo, Anna!'
console.log(gruss('Bob', 'en')); // 'Hello, Bob!'
console.log(gruss(undefined, 'en')); // 'Hello, Gast!' — undefined triggert Default
console.log(gruss(null)); // 'Hallo, null!' — null ist KEIN TriggerHallo, Gast!
Hallo, Anna!
Hello, Bob!
Hello, Gast!
Hallo, null!Wichtig: nur undefined löst den Default aus, nicht null, nicht 0, nicht ''. Das ist anders als beim alten x || default-Pattern, das auch bei 0 oder '' ersetzt hätte — meist ist die undefined-only-Semantik die gewollte.
Default als Ausdruck — bei jedem Aufruf neu
Der Default kann jeder Ausdruck sein, inklusive Funktions-Aufrufe und Variablen-Referenzen. Wichtig: er wird bei jedem Aufruf neu ausgewertet.
let id = 0;
function naechsteId() { return ++id; }
function erstelleItem(id = naechsteId(), name = 'Item') {
return { id, name };
}
console.log(erstelleItem()); // { id: 1, name: 'Item' }
console.log(erstelleItem()); // { id: 2, name: 'Item' } — neuer Wert
console.log(erstelleItem(99)); // { id: 99, name: 'Item' } — kein Default{ id: 1, name: 'Item' }
{ id: 2, name: 'Item' }
{ id: 99, name: 'Item' }Spätere Default-Werte können sich auf frühere Parameter beziehen:
function rechteck(breite, hoehe = breite) {
return breite * hoehe;
}
console.log(rechteck(5)); // 25 — Quadrat
console.log(rechteck(5, 10)); // 5025
50Rest-Parameter — variadic Funktionen
...name sammelt alle restlichen Argumente in einem Array. Das ersetzt das alte arguments-Objekt, das nur Array-like war und in Arrow Functions gar nicht existiert.
function summe(...zahlen) {
return zahlen.reduce((acc, n) => acc + n, 0);
}
console.log(summe(1, 2, 3)); // 6
console.log(summe(1, 2, 3, 4, 5)); // 15
console.log(summe()); // 0
// Mit festen Parametern davor
function log(level, ...rest) {
console.log(`[${level}]`, ...rest);
}
log('INFO', 'User', 'Anna', 'eingeloggt');6
15
0
[INFO] User Anna eingeloggtRest-Parameter muss der letzte in der Parameter-Liste sein — ein zweiter Rest oder Parameter nach dem Rest ist SyntaxError.
Rest-Parameter vs. arguments
Drei Unterschiede:
- Rest ist ein echtes Array (
.map,.filter,.reduce).argumentsist Array-like — man mussArray.from(arguments)aufrufen. - Rest funktioniert auch in Arrow Functions.
argumentsnicht. - Rest sammelt nur die restlichen Argumente — nicht alle. Das ist meist genau das, was man will.
// Klassisch mit arguments — umständlich
function alt() {
const liste = Array.from(arguments);
return liste.map(x => x * 2);
}
console.log(alt(1, 2, 3)); // [ 2, 4, 6 ]
// Modern mit Rest
function neu(...args) {
return args.map(x => x * 2);
}
console.log(neu(1, 2, 3)); // [ 2, 4, 6 ]
// Arrow + Rest — geht problemlos
const arrowFn = (...args) => args.map(x => x * 2);
console.log(arrowFn(1, 2, 3));[ 2, 4, 6 ]
[ 2, 4, 6 ]
[ 2, 4, 6 ]In neuem Code: immer Rest-Parameter, niemals arguments. Letzteres ist Legacy.
Spread im Funktions-Aufruf
Dieselbe ...-Syntax — diesmal aber im Aufruf, nicht in der Parameter-Liste — entpackt ein Array in einzelne Argumente.
function summe(a, b, c) { return a + b + c; }
const args = [1, 2, 3];
console.log(summe(...args)); // 6 — Array entpackt
console.log(Math.max(...[5, 1, 8, 3, 7])); // 8
console.log(Math.min(...[5, 1, 8, 3, 7])); // 16
8
1Spread ersetzt das alte fn.apply(null, args)-Pattern. Spread ist kürzer, lesbarer und funktioniert auch im new-Aufruf: new MyClass(...args).
Spread + Rest — Reordering und Forwarding
Spread im Aufruf und Rest in der Parameter-Liste passen perfekt zusammen — etwa beim Forwarding von Argumenten an eine andere Funktion.
function summe(...zahlen) {
return zahlen.reduce((a, b) => a + b, 0);
}
// Wrapper, der Argumente loggt und weiterreicht
function geloggteSumme(...args) {
console.log('Aufruf mit:', args);
return summe(...args);
}
console.log(geloggteSumme(1, 2, 3, 4)); // 10
// Reordering
function getauschtSumme(a, b, ...rest) {
return summe(b, a, ...rest);
}
console.log(getauschtSumme(1, 2, 3, 4)); // 10 — Reihenfolge geändert, Resultat gleichAufruf mit: [ 1, 2, 3, 4 ]
10
10Forwarding ist eines der häufigsten Muster: Decorator-Pattern, Wrapper-Funktionen, Higher-Order-Componenten. Mit Rest+Spread bleibt der Code unabhängig von der genauen Signatur der inneren Funktion.
Destructuring + Default — Named Parameters simulieren
JavaScript hat keine echten Named Parameters wie Python. Eine Konvention erreicht denselben Effekt: Object-Destructuring mit Default-Werten im Parameter-Slot.
function erstelleUser({
name = 'Anonym',
alter = 0,
aktiv = true,
rolle = 'user',
} = {}) {
return { name, alter, aktiv, rolle };
}
// Alle Defaults
console.log(erstelleUser());
// Einzelne überschreiben — Reihenfolge egal
console.log(erstelleUser({ rolle: 'admin', name: 'Anna' }));
// Nur eine Option
console.log(erstelleUser({ alter: 30 }));{ name: 'Anonym', alter: 0, aktiv: true, rolle: 'user' }
{ name: 'Anna', alter: 0, aktiv: true, rolle: 'admin' }
{ name: 'Anonym', alter: 30, aktiv: true, rolle: 'user' }Das = {} am Ende ist wichtig: ohne es würde erstelleUser() versuchen, undefined zu destructurieren — und mit TypeError scheitern. Mit = {} ist der Aufruf ohne Argumente erlaubt.
function.length — Defaults zählen nicht
Die .length-Property einer Funktion gibt die Anzahl der Parameter ohne Default und vor dem ersten Default zurück.
function a(x, y, z) {}
function b(x, y = 1, z) {}
function c(x = 1, y, z) {}
function d(...rest) {}
function e(x, ...rest) {}
console.log(a.length); // 3
console.log(b.length); // 1 — alles ab erstem Default wird ignoriert
console.log(c.length); // 0
console.log(d.length); // 0 — Rest zählt nicht
console.log(e.length); // 13
1
0
0
1Praktisch relevant für Reflection-Code (Dependency-Injection-Container, AOP-Frameworks), die auf .length reagieren.
Defaults haben TDZ-ähnliches Verhalten
Default-Werte werden in einer eigenen Scope-Ebene ausgewertet, bevor der Function-Body startet. Spätere Parameter sehen frühere — frühere sehen spätere nicht.
// OK: y kann auf x verweisen
function ok(x = 1, y = x + 1) {
return [x, y];
}
console.log(ok()); // [ 1, 2 ]
// Fehler: x verweist auf späteres y → ReferenceError
try {
function bug(x = y, y = 1) {}
bug();
} catch (e) {
console.log('TDZ:', e.message);
}[ 1, 2 ]
TDZ: Cannot access 'y' before initializationDas Verhalten ist analog zu let in einem Block — Reihenfolge der Auswertung ist von links nach rechts.
Interessantes
Default greift nur bei undefined, nicht bei null
fn(undefined) löst Default aus, fn(null) nicht — null landet als Wert null. Das ist Spec-Verhalten und unterscheidet sich vom alten x = x || default-Pattern, das auch 0 oder '' als „leer" gewertet hätte.
Default wird PRO Aufruf neu ausgewertet
function f(arr = []) erzeugt bei jedem Aufruf ein neues Array. Anders als in Python (wo der Default geteilt wird und mutable Defaults eine berüchtigte Falle sind), ist JS hier sicher — jeder Aufruf bekommt seinen eigenen Default-Wert.
Rest muss letzter Parameter sein — sonst SyntaxError
function f(...args, last) {} wirft SyntaxError: Rest parameter must be last formal parameter. Klingt trivial, aber bei Refactors leicht übersehen. Logisch: Rest sammelt „alles, was übrig ist" — danach kann definitionsgemäß nichts mehr kommen.
Spread ist nicht nur für Funktionen — auch Array/Object-Literale
const arr2 = [...arr1, 4, 5] und const obj2 = {...obj1, key: 'wert'} nutzen dieselbe Syntax für Array- und Object-Spreading. In Funktions-Aufrufen ist es derselbe Operator, nur in einem anderen Kontext. Object-Spread ist seit ES2018 spezifiziert.
function.length zählt nur bis zum ersten Default
function f(a, b = 1, c) {} hat f.length === 1 — alles ab erstem Default zählt nicht mehr, auch der erforderliche c. Das ist eine Spec-Eigenheit, die Tooling-Autoren überrascht. Wenn man tatsächliche Parameter-Anzahl will: Function-Source parsen oder eigene Metadaten pflegen.
Destructuring im Parameter — kein Default ohne = {}
function f({a, b}) {} wirft TypeError bei Aufruf ohne Argument, weil undefined nicht destructurierbar ist. Lösung: function f({a, b} = {}) {} — der Default {} wird destrukturiert und liefert a, b als undefined. Idiomatischer Pattern für Optionen-Object.
Spread mit non-iterable wirft TypeError
fn(...123) oder fn(...{a:1}) wirft TypeError: x is not iterable. Spread im Funktions-Aufruf nutzt das Iterator-Protokoll — Objekte ohne Symbol.iterator gehen nicht. Object-Spread in {...}-Literalen ist davon verschieden, das nutzt Own-Property-Enumeration.
Default-Wert kann sich auf vorherige Parameter beziehen
function f(a, b = a * 2) ist gültig — b defaultet auf das Doppelte von a. Spätere können auf frühere greifen, aber nicht umgekehrt. Das ist auf TDZ-Mechanik zurückzuführen und erlaubt sinnvolle Pattern wie rechteck(breite, hoehe = breite) für Quadrate als Default.
Weiterführende Ressourcen
Externe Quellen
- Default parameters – MDN
- Rest parameters – MDN
- Spread syntax – MDN
- Function Parameter Lists – ECMAScript Spec