W JavaScript wybór między var, let a const ma kluczowe znaczenie dla czytelności, bezpieczeństwa i przewidywalności kodu.
var to starsza deklaracja o zasięgu funkcyjnym i problematycznym hoistingu, let wprowadza zasięg blokowy dla zmiennych modyfikowalnych, a const blokuje ponowne przypisanie, promując stałe.
Od ES6 domyślnym standardem są let i const. Minimalizują błędy, poprawiają zarządzanie zmiennymi i zwiększają przewidywalność kodu.
Historia ewolucji deklaracji zmiennych
Przed ES6 jedyną opcją była deklaracja var, wprowadzona w pierwszych wersjach JavaScript. Pozwalała na definiowanie zmiennych w zasięgu funkcyjnym lub globalnym, co często prowadziło do konfliktów i nieoczekiwanych zachowań.
let i const pojawiły się w ES6, wprowadzając zasięg blokowy (od klamry do klamry {}) oraz wyraźne rozróżnienie na zmienne i stałe. W nowym kodzie var należy traktować jako rozwiązanie historyczne.
Zakres zmiennych (scope) – kluczowa różnica
var ma zasięg funkcyjny – zmienna jest widoczna w całej funkcji, nawet poza blokiem {} takim jak if czy for. Przykład z var:
function test() {
if (true) {
var x = 10;
}
console.log(x); // 10 – widoczne poza blokiem
}
test();
Zmienna „wycieka” poza blok if.
let i const mają zasięg blokowy – ograniczony do najbliższego bloku {}. Przykład z let:
function test() {
if (true) {
let x = 10;
}
console.log(x); // ReferenceError: x is not defined
}
test();
Zasięg blokowy zapobiega konfliktom nazw i „zanieczyszczaniu” przestrzeni nazw.
Porównanie zasięgów w tabeli:
| Deklaracja | Zasięg | Przykładowy błąd |
|---|---|---|
| var | funkcyjny/globalny | var length = 5; nadpisuje window.length w przeglądarce |
| let | blokowy | działa tylko w bloku if/for, nie wpływa na globalne obiekty |
| const | blokowy | jak let, ale z blokadą ponownego przypisania |
Hoisting – dlaczego var bywa podstępny?
Hoisting to mechanizm, w którym deklaracje var są „podciągane” na początek zasięgu funkcyjnego (inicjalizowane do undefined). Przykład hoistingu z var:
console.log(a); // undefined (hoisted)
var a = 5;
let i const też są hoistowane, ale pozostają w temporal dead zone (TDZ) – próba dostępu przed deklaracją rzuca ReferenceError. Przykład z let:
console.log(b); // ReferenceError
let b = 5;
TDZ wymusza poprawną kolejność pisania kodu i eliminuje całą klasę trudnych do wykrycia błędów.
Zmiana wartości – let vs const
let i var pozwalają na ponowne przypisanie (zmianę wartości). const blokuje ponowne przypisanie – wartość musi być ustawiona przy deklaracji. Przykład:
let name = "Marcin";
name = "Karol"; // OK
const pi = 3.14;
pi = 3.1415; // TypeError
Ważne dla typów referencyjnych (obiekty, tablice): const blokuje zmianę referencji, ale pozwala mutować zawartość. Przykład:
const arr = [1, 2];
arr.push(3); // OK: [1, 2, 3]
arr = [4, 5]; // TypeError – nowa referencja zabroniona
Zawsze używaj const dla tablic i obiektów, chyba że planujesz zmianę całej referencji – wtedy użyj let.
Ponowna deklaracja – unikaj duplikatów
var pozwala na wielokrotną deklarację w tym samym zasięgu. Przykład:
var name = "Marcin";
var name = "Karol"; // OK, nadpisuje
console.log(name); // "Karol"
let i const zabraniają ponownej deklaracji w tym samym zasięgu – próba kończy się SyntaxError.
Najlepsze praktyki – jak wybierać krok po kroku
- Domyślnie używaj const – dla stałych, konfiguracji i wartości, których nie zamierzasz nadpisywać. Ułatwia debugowanie i zapobiega przypadkowym zmianom.
- let tylko gdy naprawdę modyfikujesz – np. w pętlach
forlub przy licznikach; unikaj zakresu globalnego. - Unikaj var w nowym kodzie – brak zasięgu blokowego i hoisting zwiększają ryzyko błędów.
- W pętlach preferuj let – zmienna jest wiązana per iteracja, co eliminuje problemy z asynchronicznością.
- Dla obiektów i tablic wybieraj const + mutację metodami jak
pushczyObject.assign, a let tylko gdy musisz podmienić całą referencję. - Linting – włącz ESLint z regułą
prefer-const, aby automatycznie sugerować bezpieczniejsze deklaracje.
Przykład różnicy zachowania w pętli:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 3, 3, 3
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 0, 1, 2
}
Podejście „const-first” to mniej błędów, czytelniejszy kod i szybsze refaktoryzacje.
Typowe pułapki i błędy początkujących
Poniżej zebraliśmy najczęstsze problemy, na które warto uważać:
- globalne zmienne –
varw skryptach przeglądarkowych dodaje właściwości dowindow(np.lengthmoże nadpisaćwindow.length); - TDZ – dostęp do
let/constprzed deklaracją kończy sięReferenceError; - mutacja const – błędne przekonanie, że
constblokuje mutacje obiektów/tablic (np.push); blokuje tylko zmianę referencji; - funkcje strzałkowe – zachowują leksykalny this, ale to let/const chronią przed wyciekami zmiennych.
Który wybrać w praktyce – podsumowanie w tabeli
Najważniejsze różnice w jednym miejscu:
| Cecha | var | let | const |
|---|---|---|---|
| Zasięg | funkcyjny | blokowy | blokowy |
| Hoisting | tak (do undefined) |
tak (TDZ) | tak (TDZ) |
| Ponowne przypisanie | tak | tak | nie |
| Ponowna deklaracja | tak | nie | nie |
| Kiedy używać | utrzymanie starszego kodu | zmienne modyfikowalne | stałe, obiekty i tablice |
Wniosek praktyczny – zaczynaj od const, przełącz na let tylko przy potrzebie zmian. To standard w nowoczesnym JS (Node.js, React, Vue).
Stosując te zasady, twój kod będzie bardziej przewidywalny i mniej podatny na błędy. Eksperymentuj w konsoli przeglądarki, aby utrwalić różnice w praktyce.






