JavaScript kennt zwei syntaktische Grundformen, eine Funktion zu definieren — die Function-Declaration (function name() { ... }) und die Function-Expression (const name = function() { ... }). Sie unterscheiden sich nicht nur stilistisch: Declarations werden vollständig gehoisted und sind ab Block-Anfang aufrufbar, Expressions folgen den Hoisting-Regeln ihrer Variablen-Bindung. Daneben gibt es named function expressions, deren Name nur innerhalb der Funktion sichtbar ist — eine seltene aber praktische Form für Rekursion und Stack-Traces. Dieser Artikel klärt die Unterschiede mit Code, zeigt die typischen Fallen und gibt Empfehlungen für moderne Codebasen.
Function-Declaration
Eine Function-Declaration beginnt am Statement-Anfang mit dem Schlüsselwort function und hat einen Namen. Sie wird zur Creation Phase vollständig gehoisted — Name UND Body stehen im Scope ab Block-Anfang zur Verfügung.
// Aufruf vor der Definition — funktioniert
console.log(addiere(2, 3)); // 5
function addiere(a, b) {
return a + b;
}
// Auch Rekursion mit eigenem Namen
function fakultaet(n) {
return n <= 1 ? 1 : n * fakultaet(n - 1);
}
console.log(fakultaet(5)); // 1205
120Der Name addiere landet als Binding im umschließenden Scope (Function-Scope oder Module-Scope), nicht in einem Block-Scope.
Function-Expression
Eine Function-Expression ist ein Ausdruck, der eine Funktion liefert — typisch in einer Variablen-Zuweisung. Sie folgt den Hoisting-Regeln der Variable: const/let haben TDZ, var hat undefined-Initialisierung.
// Vor der Zeile NICHT aufrufbar:
// addiere(2, 3); // ReferenceError (TDZ)
const addiere = function(a, b) {
return a + b;
};
console.log(addiere(2, 3)); // 5
// Anonyme Expression vs. var
console.log(typeof multiplizieren); // 'undefined' — var ist gehoisted, aber undefined
var multiplizieren = function(a, b) { return a * b; };
console.log(multiplizieren(2, 3)); // 65
undefined
6Wichtig: die Function-Expression selbst hat keinen externen Namen, der zur Aufruf-Zeit verfügbar wäre. Sie wird über den Variablen-Namen angesprochen.
Anonym vs. Named Expression
Eine Function-Expression kann anonym oder benannt sein. Der Unterschied:
- Anonym:
const f = function() { ... }— der Funktions-eigene Name ist leer (f.namewird heuristisch aus der Zuweisung abgeleitet). - Named:
const f = function eigenerName() { ... }— der Name ist innerhalb der Funktion sichtbar, aber nicht außerhalb.
// Anonym
const anon = function() {};
console.log(anon.name); // 'anon' — aus der Zuweisung abgeleitet (ES2015)
// Named Expression
const benannt = function meineFunktion() {
console.log(typeof meineFunktion); // 'function' — INNEN sichtbar
};
console.log(benannt.name); // 'meineFunktion'
// console.log(meineFunktion); // ReferenceError — AUSSEN nicht sichtbar
benannt();anon
meineFunktion
functionNamed Expressions sind die einzige Form, in der eine Funktion innerhalb ihres eigenen Bodies einen stabilen Selbst-Bezug hat, ohne auf die externe Variablen-Bindung angewiesen zu sein. Praktisch bei Rekursion in Callbacks.
Rekursion mit Named Expression
Wer eine rekursive Function-Expression schreibt, sollte sie benennen. Der Variablen-Name könnte später überschrieben werden — der interne Name nicht.
const original = function fak(n) {
return n <= 1 ? 1 : n * fak(n - 1); // sicherer interner Name
};
console.log(original(5)); // 120
const aliasFalsch = function(n) {
return n <= 1 ? 1 : n * aliasFalsch(n - 1); // hängt an äußerer Variable
};
const ref = aliasFalsch; // Referenz behalten
// aliasFalsch = null; // Wenn man das tun würde, würde ref() abstürzen
console.log(ref(5)); // 120 — solange aliasFalsch nicht verändert120
120Der benannte interne Name macht die Funktion selbst-referent — sie hängt nicht von einem externen Bezeichner ab, der sich ändern könnte.
Named Expressions in Stack-Traces
Benannte Funktionen tauchen in Stack-Traces mit ihrem Namen auf — anonym definierte erscheinen als anonymous oder mit der heuristisch abgeleiteten Form.
const fehlerA = function() { throw new Error('A'); };
const fehlerB = function namedB() { throw new Error('B'); };
function trace(fn) {
try { fn(); } catch (e) { console.log(e.stack.split('\n').slice(0, 2).join('\n')); }
}
trace(fehlerA);
console.log('---');
trace(fehlerB);Error: A
at fehlerA (...)
---
Error: B
at namedB (...)Praktisch in Production: ein Trace von at namedB ist eindeutiger als at anonymous oder at <anonymous>. Bei Callback-heavy Code (Promise-Chains, Event-Handler) hilft das Debugging.
Function-Declaration in einem Block
Function-Declarations innerhalb eines Blocks (z.B. in einem if) verhalten sich in Strict Mode anders als in Sloppy Mode:
'use strict';
if (true) {
function inside() { return 'A'; }
}
try {
console.log(inside()); // ReferenceError in strict mode (block-scoped)
} catch (e) {
console.log('Strict:', e.message);
}Strict: inside is not definedIn Sloppy Mode wurde dieselbe Deklaration in vielen Engines historisch in den Function-Scope „gehoben" — Verhalten Engine-abhängig. ES2015+ Strict Mode räumt damit auf: in Blöcken erstellte function-Declarations sind block-scoped.
Empfehlung: in Blöcken niemals function name() { ... } benutzen. Stattdessen const name = () => ... — eindeutig block-scoped, kein Mode-Unterschied.
Der (function ...)-Trick
Eine Function-Declaration ist nur am Statement-Anfang gültig. Schreibt man function() {} direkt am Anfang, ohne Namen, ist das ein SyntaxError. Klammert man sie aber als Expression ein, wird sie zur anonymen Expression.
// SyntaxError als Statement:
// function() { return 1; }();
// OK als Expression — die IIFE:
const ergebnis = (function() {
return 42;
})();
console.log(ergebnis); // 4242Das ist der Kern des IIFE-Patterns (Immediately Invoked Function Expression) — Detail-Behandlung im eigenen Artikel. Klammern machen aus dem Statement-Kontext einen Expression-Kontext.
function.name — Heuristik vs. Explizit
Seit ES2015 setzt die Engine .name einer anonymen Funktion auf den Zuweisungs-Identifier. Das ist eine Heuristik, kein hartes Property.
const a = function() {};
console.log(a.name); // 'a'
const obj = {
method: function() {},
};
console.log(obj.method.name); // 'method'
// Higher-Order: Name vom Zuweisungs-Pfad
const b = (function() { return function() {}; })();
console.log(b.name); // '' — keine eindeutige Zuweisung
// Explizit benannt — immer reliable
const c = function explizit() {};
console.log(c.name); // 'explizit'a
method
explizitFür Bibliotheken, Debugger und Logger ist .name praktisch — explizite Named Expressions sind die einzige Garantie für einen stabilen Namen.
Was nehmen?
In modernen Codebasen ist die Konvention überwiegend:
- Top-level Funktionen: Function-Declaration (
function name() {}) — voll gehoisted, sauberer Name, gut für Stack-Traces. - Methoden in Objekten / Klassen: Methode-Shorthand (
obj = { method() {} }) — keinfunction-Keyword nötig. - Inline Callbacks: Arrow-Function (
const cb = (x) => x * 2) — kein eigenesthis, kompakt. - Selten: Named Expressions als Top-Level. Nur, wenn Rekursion auf interne Bindung angewiesen ist.
// Top-Level
function startServer(port) {
console.log('Server auf', port);
}
startServer(3000);
// Methode
const api = {
laden(id) { return `Item ${id}`; },
};
console.log(api.laden(7));
// Callback
const verdoppelt = [1, 2, 3].map(x => x * 2);
console.log(verdoppelt);Server auf 3000
Item 7
[ 2, 4, 6 ]Häufige Stolperfallen
Function-Declaration aufrufen vor der Definition geht — Expression nicht
function f() ist voll gehoisted, const f = function() hat TDZ. Wer das gewohnt ist, schreibt manchmal Code, der nur funktioniert weil die Definition zufällig vor dem Aufruf steht — und wundert sich später bei einem Refactor. Saubere Konvention: Definition vor erstem Aufruf, unabhängig von der Form.
Named Expression außen nicht sichtbar — ReferenceError
const f = function eigen() {} macht eigen NUR innerhalb des Function-Bodies bekannt. Außerhalb gibt es nur f. Wer eigen() später irgendwo anderswo aufruft, bekommt ReferenceError. Verwirrt Anfänger oft.
Function in if-Block: Strict block-scoped, Sloppy engine-abhängig
if (cond) { function f() {} } ist in Strict Mode block-scoped (nur im if sichtbar), in Sloppy Mode historisch je nach Engine unterschiedlich. Empfehlung: nie benutzen — stattdessen const f = ... innerhalb des Blocks, das ist eindeutig block-scoped.
function als Statement-Start ohne Namen ist SyntaxError
function() { return 1; }(); als Top-Level-Statement wirft SyntaxError: Function statements require a function name. Lösung: in Klammern setzen ((function() {...})()) oder einen Unary-Operator davor (!function() {...}()). Beides macht aus dem Statement einen Expression-Kontext.
Anonym vs. heuristisch genannt — name kann '' sein
ES2015 setzt .name auf den Zuweisungs-Identifier, aber nur wenn die Zuweisung eindeutig ist. const f = (function() { return function() {}; })(); liefert f.name === '' — die innere Funktion hat keinen direkten Identifier. Für reliable Naming: function explizit() {}-Form nutzen.
Function-Declarations in Modulen sind module-scoped, nicht global
Im klassischen Script-Tag (kein type="module") landeten Top-Level function-Declarations als globale Bindings. In ESM-Modulen sind sie module-scoped — nur via export nach außen sichtbar. Wer alte Script-Pattern auf Module übertragt, muss explizit exportieren.
Rekursion über äußere Variable ist fragil
const f = function(n) { return n <= 1 ? 1 : n * f(n-1); } hängt am externen f. Wenn jemand später f = null macht oder die Funktion durch eine andere Variable referenziert wird und dann f verändert, bricht die Rekursion. Named Expression löst das: const f = function fak(n) { ... n * fak(n-1); }.
Arrow-Function ist immer eine Expression, nie Declaration
Es gibt keine „Arrow-Function-Declaration". () => ... ist immer ein Ausdruck. Das heißt: Arrow Functions folgen den Hoisting-Regeln der Variable, an die sie gebunden sind — meist const mit TDZ. Wer „Function Declaration"-artige Hoisting-Eigenschaften will, muss bei function name() {} bleiben.
Weiterführende Ressourcen
Externe Quellen
- Function declaration – MDN
- Function expression – MDN
- Function – MDN Reference
- Function Definitions – ECMAScript Spec