Menu

let, const, var in JavaScript: Scope & Hoisting

Drei Wege, in JavaScript eine Variable zu deklarieren – und warum modernes Code standardmäßig const nimmt, let nur bei Bedarf und var eigentlich gar nicht mehr.

Drei Keywords, ein Zweck

In JavaScript gibt es drei Möglichkeiten, eine Variable zu deklarieren: var, let und const. Alle drei binden einen Namen an einen Wert, unterscheiden sich aber im Scope, in den Regeln zur Neuzuweisung und im Verhalten, bevor die Deklaration tatsächlich ausgeführt wird. In modernem Code nutzt man meistens const, let für Werte, die sich ändern sollen, und var praktisch gar nicht mehr.

Kurz gesagt:

index.js
Output
Click Run to see the output here.

Der Rest dieser Seite erklärt, warum sich diese drei Zeilen so verhalten, wie sie es tun.

const: die Standardwahl

Mit const legst du eine Bindung an, die sich nicht erneut zuweisen lässt. Wenn du einmal const x = 5 geschrieben hast, kannst du später nicht einfach x = 6 schreiben – die Engine wirft dir einen TypeError um die Ohren.

index.js
Output
Click Run to see the output here.

Das ist die ganze Regel. Da die meisten Variablen in einem sauber geschriebenen Programm nie neu zugewiesen werden müssen, passt const für den Großteil der Fälle. Wenn du standardmäßig zu const greifst, fallen die Stellen, an denen sich ein Wert tatsächlich ändert, sofort ins Auge.

Eine häufige Stolperfalle: const schützt die Bindung, nicht den Wert. Handelt es sich um ein Objekt oder Array, bleibt dessen Inhalt weiterhin veränderbar:

index.js
Output
Click Run to see the output here.

const bedeutet: "Dieser Name zeigt immer auf dasselbe Objekt." Es bedeutet nicht, dass das Objekt selbst unveränderlich ist. Willst du das Objekt wirklich einfrieren, ist Object.freeze(user) das Mittel der Wahl – in der Praxis verlässt man sich aber meist einfach auf die Konvention, dass man const-Objekte nicht mutiert.

let: Wenn du wirklich neu zuweisen musst

let verhält sich genauso wie const, mit einem einzigen Unterschied: Du kannst die Variable neu zuweisen. Nimm es für Zähler, Akkumulatoren, Schleifenvariablen und überall dort, wo sich ein Wert tatsächlich im Laufe der Zeit ändert.

index.js
Output
Click Run to see the output here.

Wenn du let schreibst und die Variable später nie neu zuweist, mach lieber ein const draus. In den meisten Projekten meckert der Linter ohnehin genau an dieser Stelle.

Block Scope in JavaScript

Sowohl let als auch const sind block-scoped, gelten also nur innerhalb ihres Blocks. Ein Block ist alles, was zwischen { und } steht – der Rumpf eines if, einer for-Schleife, einer Funktion oder auch einfach ein eigenständiger { ... }-Block. Eine Variable, die innerhalb eines Blocks deklariert wird, existiert außerhalb davon schlicht nicht.

index.js
Output
Click Run to see the output here.

Genau das willst du. So bleiben Variablen auf den Bereich beschränkt, in dem sie gebraucht werden, und versehentliche Namenskollisionen sind ausgeschlossen.

var macht das nicht – und genau deshalb solltest du es vermeiden.

var: Function Scope und seine Tücken

var ist an die nächste Funktion gebunden, nicht an den nächsten Block. Eine var-Deklaration innerhalb eines if- oder for-Blocks sickert also nach außen in die umgebende Funktion durch:

index.js
Output
Click Run to see the output here.

Genau dieses lockere Scoping ist die Ursache einer langen Reihe klassischer JavaScript-Bugs — der Klassiker schlechthin sind Schleifen, in denen sich alle Iterationen dasselbe var i teilen und am Ende jeder Callback denselben finalen Wert sieht.

Dazu kommt: Mit var kannst du denselben Namen im selben Scope beliebig oft neu deklarieren, ohne dass JavaScript mucken würde — Tippfehler bleiben so gnadenlos unbemerkt:

index.js
Output
Click Run to see the output here.

Moderner Code setzt fast ausschließlich auf let und const. var begegnet dir höchstens noch in Alt-Projekten, betagten Stack-Overflow-Antworten oder Skripten, die noch in uralten Umgebungen laufen müssen.

Hoisting und die Temporal Dead Zone in JavaScript

Alle drei Deklarationen werden gehoistet – die Engine weiß also schon vor der Ausführung des Blocks von ihnen – aber sie verhalten sich unterschiedlich, solange die Zeile mit der Deklaration noch nicht durchlaufen wurde.

var wird gehoistet und automatisch mit undefined initialisiert. Du kannst darauf also zugreifen, bevor die var-Zeile überhaupt erreicht wurde, ohne dass ein Fehler fliegt:

index.js
Output
Click Run to see the output here.

let und const werden zwar gehoistet, aber nicht initialisiert. Greifst du vor der Deklaration darauf zu, fliegt dir ein Fehler um die Ohren. Dieses Zeitfenster zwischen dem Betreten des Scopes und dem tatsächlichen Ausführen der Deklaration nennt man die Temporal Dead Zone (kurz TDZ):

index.js
Output
Click Run to see the output here.

Die TDZ ist ein Feature, kein Bug. Sie verwandelt "vor der Deklaration benutzt" von einem stillen undefined in einen lauten Fehler – und fängt damit jede Menge Tippfehler und Reihenfolge-Patzer ab.

const als Schleifenvariable in for...of

Ein kleiner, aber häufiger Fall: for...of-Schleifen erzeugen bei jedem Durchlauf ein frisches Binding. Deshalb darfst du const für die Schleifenvariable verwenden, auch wenn sie sich zwischen den Iterationen "ändert".

index.js
Output
Click Run to see the output here.

Jede Iteration bekommt ihre eigene name-Bindung – es gibt also keine einzelne Variable, die neu zugewiesen wird. Eine klassische for-Schleife vom Typ for (let i = 0; i < n; i++) braucht dagegen weiterhin let, denn i ist eine einzige Bindung, die hochgezählt wird.

Eine Faustregel für die Praxis

Wähle deine Deklarationen in dieser Reihenfolge:

  • const als Standard. Wenn der Wert ohnehin nicht neu zugewiesen wird, sag das auch explizit.
  • let, wenn sich die Bindung tatsächlich ändern muss.
  • var nur dann, wenn du in einer Codebasis arbeitest, die es zwingend verlangt.

Hältst du dich konsequent daran, sieht man jeder Variable auf einen Blick an, was sie ist: neu zuweisbar oder nicht, gebunden an diesen Block oder an die gesamte Funktion.

index.js
Output
Click Run to see the output here.

MAX_RETRIES und users ändern sich nie — also const. successful wächst mit der Zeit — daher let. user ist in jedem Schleifendurchlauf eine frische Bindung — wieder const. Wer den Code von oben nach unten liest, erkennt sofort, welche Werte sich bewegen und welche fix sind, ohne den Code überhaupt auszuführen.

Als Nächstes: Primitive Datentypen

Jetzt weißt du, wie man Variablen in JavaScript deklariert. Die nächste logische Frage: Welche Art von Werten können darin eigentlich landen? JavaScript kommt mit einer überschaubaren Menge an primitiven Typen — Zahlen, Strings, Booleans und ein paar weitere — und jeder davon hat seine eigenen Eigenheiten. Darum geht's auf der nächsten Seite.

Häufig gestellte Fragen

Was ist der Unterschied zwischen let, const und var in JavaScript?

const und let sind block-scoped und kamen mit ES2015 dazu, var dagegen ist function-scoped und gehört zum alten Teil der Sprache. Eine const-Bindung kann man nicht neu zuweisen, eine let-Bindung schon. var wird außerdem gehoistet und direkt mit undefined initialisiert – let und const werden zwar auch gehoistet, sind aber bis zur Deklarationszeile nicht benutzbar (die sogenannte Temporal Dead Zone).

Macht const einen Wert in JavaScript unveränderlich?

Nein. const verhindert nur die Neuzuweisung der Bindung selbst. Wenn der Wert ein Objekt oder ein Array ist, kannst du den Inhalt weiterhin verändern – const user = {}; user.name = 'Ada' ist völlig okay. Wer echte Unveränderlichkeit braucht, greift zu Object.freeze oder einer entsprechenden Library.

Sollte man in modernem JavaScript überhaupt noch var verwenden?

So gut wie nie. let und const haben klarere Scope-Regeln und fangen mehr Bugs schon beim Parsen ab. var begegnet dir eigentlich nur noch in Legacy-Code oder in den seltenen Skripten, die in Umgebungen ohne ES2015-Support laufen müssen.

Lerne mit Coddy zu programmieren

LOS GEHT'S