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.

JavaScript declaration.js
// 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)); // 120
Output
5
120

Der 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.

JavaScript expression.js
// 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)); // 6
Output
5
undefined
6

Wichtig: 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.name wird heuristisch aus der Zuweisung abgeleitet).
  • Named: const f = function eigenerName() { ... } — der Name ist innerhalb der Funktion sichtbar, aber nicht außerhalb.
JavaScript anonym-vs-named.js
// 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();
Output
anon
meineFunktion
function

Named 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.

JavaScript rekursion-named.js
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ändert
Output
120
120

Der 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.

JavaScript stack-traces.js
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);
Output
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:

JavaScript function-in-block.js
'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);
}
Output
Strict: inside is not defined

In 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.

JavaScript klammer-trick.js
// SyntaxError als Statement:
// function() { return 1; }();

// OK als Expression — die IIFE:
const ergebnis = (function() {
    return 42;
})();

console.log(ergebnis); // 42
Output
42

Das 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.

JavaScript function-name.js
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'
Output
a
method

explizit

Fü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() {} }) — kein function-Keyword nötig.
  • Inline Callbacks: Arrow-Function (const cb = (x) => x * 2) — kein eigenes this, kompakt.
  • Selten: Named Expressions als Top-Level. Nur, wenn Rekursion auf interne Bindung angewiesen ist.
JavaScript moderne-konvention.js
// 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);
Output
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

/ Weiter

Zurück zu Funktionen

Zur Übersicht