Wysoki kąt widzenia klawiatury komputerowej

Var, let czy const – różnice w deklarowaniu zmiennych w JavaScript

5 min. czytania

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

  1. Domyślnie używaj const – dla stałych, konfiguracji i wartości, których nie zamierzasz nadpisywać. Ułatwia debugowanie i zapobiega przypadkowym zmianom.
  2. let tylko gdy naprawdę modyfikujesz – np. w pętlach for lub przy licznikach; unikaj zakresu globalnego.
  3. Unikaj var w nowym kodzie – brak zasięgu blokowego i hoisting zwiększają ryzyko błędów.
  4. W pętlach preferuj let – zmienna jest wiązana per iteracja, co eliminuje problemy z asynchronicznością.
  5. Dla obiektów i tablic wybieraj const + mutację metodami jak push czy Object.assign, a let tylko gdy musisz podmienić całą referencję.
  6. 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 zmiennevar w skryptach przeglądarkowych dodaje właściwości do window (np. length może nadpisać window.length);
  • TDZ – dostęp do let/const przed deklaracją kończy się ReferenceError;
  • mutacja const – błędne przekonanie, że const blokuje 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.

Emil Jarecki
Emil Jarecki

Pasjonat technologii i analityk cyfrowej rzeczywistości. Na blogu poruszam tematykę z pogranicza IT i biznesu. Piszę o AI, cyberbezpieczeństwie i finansach, testuję sprzęt i analizuję trendy w social mediach. W wolnych chwilach sprawdzam nowości w świecie gier i płatności cyfrowych. Pomagam zrozumieć technologię, by służyła nam lepiej i bezpieczniej.