Temat javascript let sprowadza się do jednej praktycznej rzeczy: lepszego panowania nad zmiennymi, które żyją tylko tam, gdzie naprawdę są potrzebne. To ważne zwłaszcza w frontendzie, bo drobny błąd zasięgu potrafi przełożyć się na źle działający przycisk, licznik, formularz albo logikę komponentu. W tym tekście pokazuję, jak działa `let`, kiedy ma przewagę nad `var`, kiedy ustąpić miejsca `const` i jak używać tej deklaracji tak, żeby kod był czytelny, stabilny i łatwiejszy do utrzymania.
Najkrócej: `let` porządkuje zmienne, które zmieniają się tylko wewnątrz bloku
- `let` tworzy zmienne o zasięgu blokowym, więc nie wypływają poza nawiasy `{}`.
- Najczęściej używa się go wtedy, gdy wartość ma się zmieniać, ale tylko lokalnie.
- W nowym kodzie frontendowym zwykle lepszym domyślnym wyborem jest `const`, a dopiero potem `let`.
- `var` nadal bywa spotykane w starszych projektach, ale częściej wprowadza błędy niż pomaga.
- W interfejsach `let` szczególnie dobrze sprawdza się w pętlach, callbackach i krótkotrwałym stanie UI.
- Najczęstsze pułapki to użycie zmiennej przed deklaracją, redeklaracja w tym samym zakresie i mylenie `let` ze stanem aplikacji.
Co robi `let` i dlaczego zasięg blokowy ma znaczenie
`let` deklaruje zmienną, która istnieje tylko w obrębie bloku, w którym została utworzona. Blokiem jest na przykład kod wewnątrz `if`, `for`, `switch`, `try...catch` albo po prostu fragment ujęty w nawiasy klamrowe. Dla mnie to jedna z tych cech JavaScriptu, które od razu poprawiają jakość kodu, bo zmienna przestaje „przeciekać” do reszty programu.
W praktyce oznacza to prostą rzecz: jeśli deklaruję `let` wewnątrz funkcji, pętli albo gałęzi warunkowej, to poza tym miejscem tej zmiennej już nie ma. To ogranicza ryzyko przypadkowego nadpisania wartości i ułatwia myślenie o logice interfejsu. Gdy buduję UI, wolę takie lokalne, przewidywalne zmienne niż coś, co żyje dłużej, niż faktycznie powinno.
if (isMenuOpen) {
let label = 'Zamknij menu';
console.log(label);
}
console.log(label); // ReferenceErrorTen efekt jest szczególnie przydatny, gdy pracuję nad małymi fragmentami interakcji: przełącznikiem, walidacją formularza albo logiką widoku, który ma zmieniać się tylko na chwilę. To właśnie ta granica między blokiem a resztą kodu robi największą różnicę, gdy porównuję `let` z innymi deklaracjami.

Jak `let` wypada na tle `var` i `const`
Jeśli miałbym uprościć wybór do jednej reguły, powiedziałbym tak: najpierw `const`, potem `let`, a `var` tylko wtedy, gdy pracuję ze starym kodem. Taki porządek zwykle daje najlepszą czytelność, a jednocześnie zmniejsza liczbę błędów wynikających z przypadkowego nadpisywania wartości.
| Cecha | `let` | `var` | `const` | Kiedy użyć |
|---|---|---|---|---|
| Zasięg | blokowy | funkcyjny | blokowy | Gdy chcesz ograniczyć widoczność do konkretnego fragmentu kodu |
| Możliwość ponownego przypisania | tak | tak | nie | Gdy wartość ma się zmieniać w trakcie działania |
| Ryzyko przeciekania poza blok | niskie | wysokie | niskie | Gdy zależy Ci na porządku w logice UI |
| Typowe zastosowanie | liczniki, warunki, lokalny stan | stary kod, projekty dziedziczone | stałe referencje, dane, selektory | Dobór deklaracji do roli zmiennej |
| Zachowanie na poziomie globalnym | nie tworzy właściwości obiektu globalnego | często tworzy | nie tworzy | Gdy pracujesz w przeglądarce i chcesz uniknąć bałaganu |
Według dokumentacji MDN, jeśli zmienna nie ma być ponownie przypisywana, rozsądniej jest wybrać `const` zamiast `let`. Ja też tak pracuję, bo to zmusza mnie do lepszego myślenia o tym, co naprawdę ma się zmieniać, a co powinno pozostać nienaruszone. Kiedy to uporządkujesz, łatwiej zobaczyć, gdzie `let` realnie pomaga w interfejsie, a gdzie tylko udaje rozwiązanie problemu.
Gdzie `let` pomaga najbardziej w frontendzie i UX
W pracy nad interfejsem `let` najczęściej pojawia się tam, gdzie coś ma zmieniać się lokalnie i tylko przez chwilę. To mogą być liczniki, indeksy pętli, flagi typu `isOpen`, stan walidacji formularza albo pomocnicza zmienna potrzebna w callbacku. Z perspektywy UX to ważne, bo drobny błąd zasięgu potrafi dać bardzo widoczny efekt: nie ten element zostanie zaznaczony, zła karta się otworzy albo licznik pokaże błędną wartość.
Pętle i generowanie dynamicznych elementów
Jeśli renderuję listę kart, przycisków albo zakładek, `let` w pętli pozwala mi utrzymać poprawną wartość indeksu dla każdej iteracji. To ma znaczenie szczególnie wtedy, gdy do elementów dopinam zdarzenia. Każda iteracja dostaje własne wiązanie zmiennej, więc callback nie „gubi” kontekstu.
const tabs = document.querySelectorAll('[data-tab]');
for (let index = 0; index < tabs.length; index++) {
tabs[index].addEventListener('click', () => {
activateTab(index);
});
}Przy `var` taki kod częściej kończy się błędami, bo zmienna nie jest ograniczona do pojedynczego obiegu pętli. W interfejsie użytkownik nie widzi tego jako „problem techniczny” - on po prostu klika w kartę i otwiera mu się coś nie tego, co trzeba. To już bezpośrednio uderza w jakość doświadczenia.
Obsługa zdarzeń i callbacki
W event handlerach `let` świetnie sprawdza się do trzymania lokalnych danych, które mają przetrwać tylko do momentu zakończenia konkretnej akcji. Mogę w ten sposób zbudować prosty, przewidywalny przepływ: pobieram wartość, przetwarzam ją, aktualizuję widok i kończę temat. Bez nadmiarowych zmiennych globalnych, bez śladów po logice, która nie powinna żyć dłużej niż pojedynczy klik.
button.addEventListener('click', () => {
let message = 'Zapisano zmiany';
toast.show(message);
});Taka lokalność jest dobra nie tylko dla porządku w kodzie. W praktyce ułatwia też debugowanie, bo szybciej widzę, skąd bierze się dana wartość i w jakim momencie została zmieniona. To drobiazg, ale przy interfejsach z wieloma stanami robi dużą różnicę.
Lokalny stan formularzy i prostych widoków
W prostych scenariuszach `let` przydaje się do przechowywania tymczasowego stanu, na przykład informacji, czy formularz jest poprawny, jaki komunikat błędu pokazać albo czy użytkownik włączył konkretny filtr. To dobry wybór, jeśli zmienna ma zostać przeliczona kilka razy w obrębie jednej operacji, ale nie ma być trwałym stanem aplikacji.
let isValid = true;
if (!email.value.includes('@')) {
isValid = false;
}
if (!isValid) {
showError('Podaj poprawny adres e-mail');
}Tu warto zachować zdrowy umiar: jeśli zmiana zmiennej ma sterować odświeżeniem całego widoku w React, Vue czy Svelte, to `let` nie zastąpi mechanizmu stanu komponentu. Sama zmiana wartości nie wywoła renderu, więc interfejs nie zareaguje tak, jak oczekujesz. W takim miejscu `let` nadaje się do obliczeń pomocniczych, ale nie do przechowywania właściwego stanu UI.
Przeczytaj również: REST API w praktyce - Jak budować przewidywalne integracje?
Kiedy `let` nie jest dobrym substytutem stanu
To jedna z najważniejszych granic. Jeśli projektuję aplikację, a nie pojedynczy skrypt, to `let` nie powinien udawać magazynu danych. Dobrym przykładem jest licznik w widoku: lokalna zmienna może policzyć wartość, ale jeśli interfejs ma reagować na zmianę, potrzebuję mechanizmu, który rzeczywiście synchronizuje logikę z widokiem. W przeciwnym razie dostaję kod, który wygląda poprawnie, a użytkownik widzi martwy ekran.
Dlatego w frontendzie traktuję `let` jako narzędzie do krótkich, kontrolowanych zmian. Tam, gdzie liczy się trwałość danych, współdzielenie stanu między komponentami albo reakcja renderera, potrzebuję czegoś mocniejszego niż zwykłe przypisanie. To właśnie ten podział pomaga mi uniknąć fałszywego poczucia, że „zmienna się zmieniła, więc wszystko już działa”.
Kiedy już wiadomo, gdzie `let` daje realną wartość, warto spojrzeć na miejsca, w których najłatwiej popełnić kosztowny błąd.
Najczęstsze błędy, które robią zamieszanie
Najwięcej problemów nie bierze się z samego `let`, tylko z tego, że łatwo źle odczytać jego zasady. W kodzie frontendowym takie pomyłki szybko się mszczą, bo użytkownik nie zobaczy komunikatu o zasięgu blokowym - zobaczy tylko źle działający interfejs.
- Używanie zmiennej przed deklaracją - `let` ma tzw. temporal dead zone, więc przed linią deklaracji nie wolno go czytać. To nie jest zwykły „brak wartości”, tylko realny błąd.
- Mylenie `let` z `var` - `var` nie szanuje bloku tak jak `let`, więc może powodować zaskakujące przecieki zasięgu.
- Re-deklaracja w tym samym zakresie - tej samej nazwy nie można użyć drugi raz w jednym bloku. Jeśli potrzebuję nowego znaczenia, tworzę nowy blok albo zmieniam nazwę.
- Brak klamer w `switch` - w gałęziach `case` łatwo wpakować kilka deklaracji do jednego zakresu i niechcący zderzyć dwie zmienne o tej samej nazwie.
- Używanie `let` tam, gdzie wystarczy `const` - jeśli wartość nie ma się zmieniać, `let` tylko zwiększa szum poznawczy.
console.log(price); // ReferenceError
let price = 99;Hoisting w przypadku `let` działa inaczej, niż wielu początkujących zakłada. Zmienna jest „znana” dla silnika, ale nie można jej użyć zanim dotrze się do linii deklaracji. Dla mnie to ważny szczegół, bo tłumaczy, dlaczego kod może wyglądać poprawnie na pierwszy rzut oka, a mimo to wywracać się w runtime.
switch (type) {
case 'basic': {
let label = 'Podstawowy';
break;
}
case 'pro': {
let label = 'Rozszerzony';
break;
}
}Te dodatkowe nawiasy przy `switch` nie są ozdobą. Chronią zakres każdej gałęzi i pozwalają deklarować lokalne zmienne bez konfliktów. Gdy te pułapki masz z głowy, zostaje już tylko uprościć styl pracy z deklaracjami, żeby kod był stabilny także po kilku refaktorach.
Jak pisać czytelniejszy kod z `let` bez nadmiaru ruchomych części
Najlepszy sposób użycia `let` to nie „używać go jak najczęściej”, tylko używać go tam, gdzie naprawdę wnosi porządek. W praktyce stosuję kilka prostych zasad, które dobrze działają zarówno w małych widokach, jak i w większych frontowych aplikacjach.
- Domyślnie wybieraj `const` - jeśli zmienna nie zmienia wartości, nie dawaj sobie sztucznej swobody.
- Ograniczaj zasięg do minimum - deklaruj zmienną możliwie blisko miejsca użycia.
- Nazywaj zmienne po roli, nie po typie - `selectedIndex`, `isOpen`, `errorMessage` mówią więcej niż nazwy techniczne.
- Nie przechowuj w `let` danych, które powinny żyć w stanie komponentu - to częsty błąd w aplikacjach SPA.
- Jeśli zmienna zmienia się tylko w jednym przebiegu logiki, zostaw ją lokalnie - dzięki temu łatwiej ją zrozumieć i przetestować.
Ja traktuję `let` jako narzędzie do krótkich, kontrolowanych zmian. Daje mi elastyczność, ale nie zachęca do bałaganu, jeśli pilnuję zasięgu i jasno rozróżniam zmienne pomocnicze od prawdziwego stanu aplikacji. To właśnie dlatego ten keyword tak dobrze współgra z dobrym frontendem: porządkuje logikę, zanim jeszcze kod trafi do przeglądarki.
Im mniej zmienne wychodzą poza swój blok, tym mniej niespodzianek pojawia się później w interfejsie. Jeśli trzymasz się tej zasady, `let` staje się prostym i skutecznym sposobem na bardziej przewidywalny kod, a nie kolejną techniczną sztuczką do zapamiętania.
