W starym kodzie JavaScriptu słowo kluczowe var pojawia się bardzo często, ale jego działanie potrafi zaskoczyć nawet osoby znające let i const. Wyjaśniam, czym różni się deklaracja za pomocą var, jak działa hoisting i zakres funkcji oraz kiedy użycie tego rozwiązania w aplikacji webowej może prowadzić do trudnych do wykrycia błędów.
Najważniejsze zasady używania var w JavaScripcie
-
Zakres funkcji oznacza, że
varnie respektuje zwykłego zakresu blokowego. - Hoisting przenosi deklarację zmiennej na początek zakresu, ale nie jej przypisaną wartość.
- let i const są dziś zazwyczaj lepszym wyborem w nowym kodzie.
-
Powtórna deklaracja zmiennej za pomocą
varjest dozwolona w tym samym zakresie. -
Stary kod, skrypty i kompatybilność to główne sytuacje, w których nadal można spotkać uzasadnione użycie
var.
Co oznacza var i dlaczego nadal się z nim spotykamy
var służy do deklarowania zmiennych, czyli nazw, pod którymi JavaScript przechowuje wartości. Można zadeklarować zmienną bez wartości albo od razu przypisać jej dane.
var username;
var points = 120;
username = "Ania";
points = points + 30;Pierwsza zmienna ma początkowo wartość undefined. Druga otrzymuje liczbę już w chwili deklaracji. W praktyce nie trzeba określać typu danych, ponieważ JavaScript robi to dynamicznie podczas działania programu.
To najstarszy sposób deklarowania zmiennych w tym języku. Przed wprowadzeniem let i const w standardzie ECMAScript 2015 programiści korzystali właśnie z var. Dlatego starsze biblioteki, skrypty osadzone w stronach i część kodu produkcyjnego nadal go używają.
Sam fakt, że kod zawiera var, nie oznacza jeszcze błędu. Problem zaczyna się wtedy, gdy programista zakłada, że zmienna zachowuje się jak deklarowana wewnątrz bloku. Największa różnica dotyczy zakresu widoczności, a nie samego przechowywania wartości.
Zakres funkcji zmienia sposób działania zmiennej
Zmienna zadeklarowana za pomocą var ma zakres funkcji. Jeśli znajduje się wewnątrz funkcji, jest dostępna w całej tej funkcji, nawet gdy zapisano ją wewnątrz instrukcji if, pętli albo innego bloku.
function showMessage() {
if (true) {
var message = "Operacja zakończona";
}
console.log(message);
}
showMessage();Ten kod zadziała, ponieważ blok if nie tworzy dla var osobnego zakresu. Zmienna jest widoczna w całej funkcji showMessage. Dla porównania let i const są ograniczone do bloku, w którym zostały zadeklarowane.
if (true) {
let localMessage = "Tylko w tym bloku";
const status = "OK";
}
console.log(localMessage); // ReferenceError
console.log(status); // ReferenceErrorW aplikacji webowej różnica może ujawnić się przy obsłudze formularzy, filtrów lub warunkowych fragmentów interfejsu. Zmienna, która miała istnieć tylko przez kilka instrukcji, zaczyna być dostępna dalej i może przypadkowo zostać nadpisana. Z mojego doświadczenia wynika, że wyciekanie zmiennych poza blok jest jednym z najczęstszych powodów, dla których stary kod JavaScriptu staje się trudny w utrzymaniu.
Jeżeli var zostanie użyte poza funkcją w klasycznym skrypcie, trafia do zakresu globalnego. W przeglądarce może wtedy stać się właściwością obiektu window. W module JavaScriptu zasady są inne, ponieważ najwyższy poziom modułu ma własny zakres i nie tworzy automatycznie globalnej właściwości.
Hoisting wyjaśnia pozornie dziwne wyniki
Hoisting, nazywany też wynoszeniem deklaracji, oznacza, że deklaracje var są przetwarzane przed wykonaniem kodu w danym zakresie. Nie oznacza to jednak, że JavaScript przenosi na początek również wartość przypisaną do zmiennej.
console.log(total); // undefined
var total = 50;Silnik JavaScriptu interpretuje ten fragment w przybliżeniu tak:
var total;
console.log(total); // undefined
total = 50;Dlatego odwołanie do zmiennej przed linią deklaracji nie kończy się tutaj błędem ReferenceError. Zmienna istnieje, ale nie ma jeszcze przypisanej wartości. Hoisting dotyczy deklaracji, nie inicjalizacji.
Właśnie ten mechanizm często utrudnia czytanie kodu. Program może działać, mimo że kolejność instrukcji sugeruje błąd. Nie traktowałbym tego jako wygodnej cechy, na której warto budować logikę aplikacji. Czytelniejsza praktyka polega na deklarowaniu zmiennej przed pierwszym użyciem.
W przypadku let i const również mówi się o hoistingu, ale przed wykonaniem deklaracji obowiązuje tak zwana temporal dead zone. Próba odwołania się do zmiennej przed jej deklaracją kończy się błędem. Taka restrykcja szybciej ujawnia pomyłki w kodzie.
Porównanie var, let i const w praktyce
W nowym kodzie aplikacji webowych wybór słowa kluczowego powinien wynikać z tego, czy zmienna będzie ponownie przypisywana i jak długo ma być widoczna. Poniższe zestawienie pokazuje najważniejsze różnice.
| Cecha | var | let | const |
|---|---|---|---|
| Zakres | Funkcja lub globalny skrypt | Blok | Blok |
| Zmiana wartości | Dozwolona | Dozwolona | Niedozwolona po przypisaniu |
| Brak wartości początkowej | Dozwolony | Dozwolony | Niedozwolony |
| Powtórna deklaracja w tym samym zakresie | Dozwolona | Błąd składni | Błąd składni |
| Odwołanie przed deklaracją | Zwraca undefined
|
Błąd wykonania | Błąd wykonania |
Najczęściej zaczynam od const. Jeśli wiem, że zmienna będzie otrzymywać nowe wartości, wybieram let. Po var sięgam przede wszystkim wtedy, gdy pracuję z istniejącym kodem, konkretnym ograniczeniem środowiska albo biblioteką, która wymaga starszego stylu.
Trzeba przy tym pamiętać, że const nie zamraża obiektu ani tablicy. Blokuje ponowne przypisanie zmiennej, ale zawartość obiektu nadal może się zmienić.
const user = {
name: "Marek"
};
user.name = "Piotr"; // Działa
// user = {}; // TypeErrorTo rozróżnienie jest ważne w aplikacjach korzystających z danych formularza, konfiguracji i obiektów stanu. Stała referencja nie zawsze oznacza niezmienne dane.
Typowe błędy związane z var
Zmiana wartości poza zamierzonym blokiem
var isVisible = false;
if (userIsLoggedIn) {
var isVisible = true;
}
console.log(isVisible);Powtórna deklaracja nie tworzy nowej zmiennej ograniczonej do if. Modyfikuje tę samą zmienną. Przy większej funkcji taki kod może powodować trudne do odtworzenia błędy interfejsu, na przykład nieprawidłowe pokazywanie panelu użytkownika.
Powtórna deklaracja przez przypadek
var apiUrl = "/api/products";
var apiUrl = "/api/orders";
console.log(apiUrl); // "/api/orders"JavaScript dopuszcza taki zapis, ale druga deklaracja może ukrywać zwykłą pomyłkę. Przy let i const podobny błąd jest zatrzymywany już na etapie parsowania kodu. To jedna z małych rzeczy, które w codziennej pracy robią dużą różnicę.
Problem z pętlami i funkcjami zwrotnymi
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}Wynik to zwykle trzy razy 3, ponieważ wszystkie funkcje zwrotne korzystają z tej samej zmiennej i. Pętla kończy działanie, zanim opóźnione funkcje zostaną wykonane.
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}Wersja z let tworzy osobny zakres dla każdej iteracji, więc wypisuje 0, 1 i 2. Ten przykład dobrze pokazuje, że różnica między deklaracjami nie jest wyłącznie kwestią stylu. Może zmienić wynik działania kodu asynchronicznego.
Kiedy var ma jeszcze sens
W nowym projekcie najczęściej nie ma powodu, aby wybierać var zamiast let lub const. Nowoczesny standard JavaScriptu, moduły i narzędzia budujące aplikacje dobrze wspierają nowsze deklaracje.
Istnieją jednak sytuacje, w których jego obecność jest uzasadniona:
- utrzymanie starszego kodu, którego nie chcemy od razu przepisywać,
- zgodność ze starym środowiskiem lub ograniczonym narzędziem JavaScript,
- analiza dawnych bibliotek, w których zakres funkcji był świadomym elementem projektu,
- kod generowany przez starszy kompilator albo narzędzie transpilujące.
Nie przepisywałbym mechanicznie każdego var na let. Najpierw sprawdzam, czy zmienna nie jest wykorzystywana poza blokiem, czy nie zależy od powtórnej deklaracji oraz czy kod nie opiera się na specyficznym zachowaniu pętli. Automatyczna zamiana bez testów może zmienić działanie programu.
W nowej funkcji przyjmuję prostą regułę. Używam const, gdy nie przypisuję zmiennej nowej wartości. Wybieram let, gdy wartość ma się zmieniać. var zostawiam dla kodu, który rzeczywiście wymaga jego semantyki albo jest częścią starszego rozwiązania.
Jak bezpiecznie pracować z var w istniejącej aplikacji
Jeśli utrzymujesz starszą aplikację, zacznij od znalezienia zakresu każdej zmiennej. Sprawdź, czy deklaracja znajduje się w funkcji, czy na poziomie globalnym, oraz czy ta sama nazwa nie jest używana w kilku warunkach i pętlach.
- Znajdź wszystkie deklaracje
varw danej funkcji. - Sprawdź miejsca odwołań przed deklaracją.
- Poszukaj powtórnych deklaracji tej samej nazwy.
- Przeanalizuj pętle z funkcjami zwrotnymi i kodem asynchronicznym.
- Dopiero potem rozważ zamianę na
letalboconst.
Podczas refaktoryzacji szczególnie dokładnie testuję formularze, obsługę zdarzeń i fragmenty uruchamiane po czasie. To tam zakres zmiennej najczęściej wpływa na zachowanie użytkownika, choć sam kod wygląda na pozornie prosty.
Pomocne są też reguły lintingu, które ostrzegają przed użyciem var, niezdefiniowanymi zmiennymi i deklaracjami przesłaniającymi inne nazwy. Nie zastępują one rozumienia kodu, ale dobrze wyłapują powtarzalne problemy jeszcze przed uruchomieniem aplikacji.
Najlepsza decyzja zależy od wieku i roli kodu
var nie jest bezużyteczne, ale wymaga świadomości jego historycznych zasad. Zakres funkcji, hoisting i możliwość ponownej deklaracji sprawiają, że łatwiej nim przypadkowo stworzyć błąd niż za pomocą let lub const.
W nowej aplikacji webowej wybrałbym const jako domyślną opcję, let dla wartości, które faktycznie się zmieniają, a var traktowałbym jako narzędzie do obsługi istniejącego kodu i konkretnych ograniczeń. Taki podział jest prosty, czytelny i dobrze chroni przed problemami z zakresem.
Najważniejsze nie jest samo zapamiętanie trzech słów kluczowych, lecz zrozumienie, kiedy zmienna ma być widoczna, czy może zmienić wartość i jak zachowa się w kodzie asynchronicznym. Gdy te trzy pytania są jasne, wybór między var, let i const przestaje być zgadywaniem.
