Let i var to dwa sposoby deklarowania zmiennych w JavaScript, które różnią się fundamentalnie pod względem zasięgu (scope), hoistingu i możliwości ponownej deklaracji. Wprowadzenie let w ES6 (2015) było odpowiedzią na problemy i nieintuicyjne zachowania var, które przez lata frustraowały programistów i prowadziły do trudnych do znalezienia bugów.
Dla developerów uczących się JavaScript lub migrujących legacy code do nowoczesnych standardów, zrozumienie różnic między let a var jest kluczowe dla pisania czystego, przewidywalnego kodu bez subtelnych błędów związanych z zakresem zmiennych.
Zasięg (Scope) – fundamentalna różnica
Var ma zasięg funkcyjny (function scope), co oznacza, że zmienna jest widoczna w całej funkcji, niezależnie od bloku w którym została zadeklarowana. Nawet jeśli zadeklarujesz var wewnątrz pętli for czy bloku if, będzie dostępna poza tym blokiem, ale tylko w ramach funkcji.
javascript
function testVar() {
if (true) {
var x = 10;
}
console.log(x); // 10 - działa!
}Let ma zasięg blokowy (block scope), co jest bardziej intuicyjne i zgodne z większością innych języków programowania. Zmienna zadeklarowana z let istnieje tylko w bloku, w którym została utworzona – wewnątrz {}.
javascript
function testLet() {
if (true) {
let y = 10;
}
console.log(y); // ReferenceError: y is not defined
}To zachowanie let eliminuje całą klasę bugów związanych z niezamierzonym wyciekaniem zmiennych poza ich logiczny kontekst.
Hoisting – wyciąganie deklaracji
Var jest hoistowany – deklaracja jest przenoszona na początek funkcji podczas kompilacji, ale inicjalizacja pozostaje w miejscu. To prowadzi do sytuacji, gdzie można odwoływać się do zmiennej przed jej deklaracją, otrzymując undefinedzamiast błędu.
javascript
console.log(a); // undefined (nie błąd!)
var a = 5;
console.log(a); // 5Let też jest hoistowany, ale znajduje się w „temporal dead zone” od początku bloku do momentu deklaracji. Odwołanie przed deklaracją powoduje ReferenceError.
javascript
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 5;
console.log(b); // 5Temporal dead zone wymusza deklarowanie zmiennych przed ich użyciem, co jest dobrą praktyką programistyczną i zapobiega subtelnym błędom.
Ponowna deklaracja
Var pozwala na ponowną deklarację tej samej zmiennej w tym samym zasięgu bez błędu. To może prowadzić do przypadkowego nadpisywania zmiennych.
javascript
var name = "Jan";
var name = "Anna"; // działa, nadpisuje bez ostrzeżenia
console.log(name); // "Anna"Let nie pozwala na ponowną deklarację w tym samym zasięgu, rzucając błąd składni. To chroni przed przypadkowym nadpisaniem.
javascript
let name = "Jan";
let name = "Anna"; // SyntaxError: Identifier 'name' has already been declaredMożesz jednak ponownie przypisać wartość do zmiennej let (reassignment), co jest różne od re-declaration:
javascript
let name = "Jan";
name = "Anna"; // OK - reassignmentPętle i zamknięcia (closures)
Var w pętlach tworzy jedną współdzieloną zmienną dla wszystkich iteracji, co prowadzi do słynnego problemu z setTimeout:
javascript
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Wypisze: 3, 3, 3 (nie 0, 1, 2!)Let w pętlach tworzy nową zmienną dla każdej iteracji, co rozwiązuje problem:
javascript
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Wypisze: 0, 1, 2 (jak oczekiwano)To zachowanie let eliminuje potrzebę IIFE (Immediately Invoked Function Expression) czy innych workarounds używanych z var.
Zmienne globalne i window object
Var w scope globalnym tworzy właściwość obiektu window w przeglądarce:
javascript
var globalVar = "test";
console.log(window.globalVar); // "test"Let w scope globalnym nie dodaje właściwości do window:
javascript
let globalLet = "test";
console.log(window.globalLet); // undefinedTo zapobiega przypadkowemu zanieczyszczaniu globalnej przestrzeni nazw i konfliktom z właściwościami window.

Kiedy używać którego
Zawsze używaj let (lub const) w nowoczesnym JavaScript. ES6+ best practices zalecają:
constjako default dla wartości, które nie będą reassignowaneletdla zmiennych, które będą reassignowanevarpraktycznie nigdy w nowym kodzie
Legacy code może wymagać używania var dla kompatybilności z bardzo starymi przeglądarkami (pre-2015), ale transpilery jak Babel pozwalają pisać nowoczesny kod z let/const kompilując do var dla starych środowisk.
Linting i Code Quality – ESLint z regułami no-var i prefer-const wymusza nowoczesne praktyki, automatycznie flagując użycie var jako code smell.
Migracja z var do let/const
Przy refaktoringu legacy code:
- Zamień wszystkie
varnalet - Przetestuj dokładnie – zmiany w scope mogą ujawnić ukryte bugi
- Zmień
letnaconstwszędzie gdzie możliwe - Uruchom testy jednostkowe i integracyjne
Automatyczne narzędzia jak ESLint z --fix mogą pomóc, ale ręczna weryfikacja jest kluczowa dla critical code.
Wprowadzenie let i const w ES6 było jedną z najważniejszych poprawek JavaScript, eliminując źródło niezliczonych bugów i frustracji. Dla każdego współczesnego projektu JavaScript, var powinien być traktowany jako przestarzały, a leti const jako standard. Block scope, temporal dead zone i zakaz re-declaration czynią kod bardziej przewidywalnym i łatwiejszym w utrzymaniu.
Bibliografia:
- „You Don’t Know JS: Scope & Closures” – Kyle Simpson (2014)
- „JavaScript: The Definitive Guide, 7th Edition” – David Flanagan (2020)
- MDN Web Docs – „let” – https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
- MDN Web Docs – „var” – https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
- „Exploring ES6” – Dr. Axel Rauschmayer (2015)
- ECMAScript 2015 Language Specification – https://www.ecma-international.org/ecma-262/6.0/
- ESLint Documentation – „no-var” rule – https://eslint.org/docs/rules/no-var












