var, let i const - hoisting i zasięg w JavaScript

JavaScript, podobnie jak wiele z tych bardziej nowoczesnych języków programowania, zapewnia szereg metod deklaracji zmiennych. W JS można ich dokonywać za pomocą słów kluczowych var
, let
i const
. Każde z nich ma swoje przeznaczenie. Artykuł ten ma na celu omówienie różnic i niuansów związanych z używaniem każdej z metod. Dla lepszego zapoznania się z tematem sprawdź definicje każdego z tych słów kluczowych w oficjalnej specyfikacji ECMAScript i MDN.
Słowo kluczowe
var
deklaruje zmienną o aktualnym kontekście wykonania, wraz z opcjonalnym zaincjalizowaniem wartością.
Słowo kluczowelet
deklaruje zmienną w zasięgu bloku, wraz z opcjonalnym zaincjalizowaniem wartością.
Słowo kluczoweconst
deklaruje stałe w zasięgu bloku, podobne do słowa kluczowegolet
, ale nie można zmienić wartości tej stałej. Deklaracjaconst
tworzy referencję typu read-only do wartości.
Windowanie (ang. Hoisting)
Jako koncept, hoisting sugeruje, że zmienne i deklaracje funkcji są przenoszone, tak aby znalazły się na początku Twojego kodu. Co się jednak naprawdę dzieje to to, że deklaracje zmiennych i funkcji są zapisywane w pamięci podczas fazy kompilacji, ale pozostają dokładnie tam, gdzie zostały wpisane w kodzie. Podstawowe znaczenie windowania polega na tym, że umożliwia ono korzystanie z funkcji przed zadeklarowaniem ich w kodzie.
Oto rzeczy, których możemy nauczyć się z definicji windowania.
- Co zostaje przeniesione, to deklaracje zmiennych i funkcji. Przypisanie zmiennych i inicjalizacja nigdy nie zostaje przeniesiona.
- Deklaracje nie są przenoszone na początek kodu; zamiast tego są zapisywane w pamięci.
W JavaScript wszystkie zmienne zdefiniowane przy użyciu var
na początku mają wartość undefined
. Jest to spowodowane windowaniem, które zapisuje w pamięci deklaracje zmiennych i inicjuje je wartością undefined
. Poniższy przykład dobrze to ilustruje:
Linijka 2 wyrzuca ReferenceError: y is not defined
Jednak zmienne zdefiniowane za pomocą słów kluczowych let
i const
, gdy są windowane, nie są inicjalizowane z undefined
. Są one raczej w stanie zwanym Temporal Dead Zone, dopóki ich definicje nie zostaną zewaluowane.
Linijka 1 wyrzuca ReferenceError ponieważ nadal znajduje się w Temporal Dead Zone
Następny fragment kodu pokazuje hoisting zmiennych let
i const
.
Linijka 3 wyrzuca ReferenceError, ponieważ x w linijce 4 zostało wywindowane wewnątrz bloku
Zmienna x
zdefiniowana w bloku za pomocą słowa kluczowego let
jest windowana i ma pierwszeństwo przed zmienną x
zdefiniowaną za pomocą var
. Jednak nadal znajduje się ona w Temporal Dead Zone, gdy występuje odwołanie do niej z console.log(x)
, a zatem wyrzuca reference error.
Zasięg
Zmienne zdefiniowane za pomocą słowa kluczowego var
mają zasięg, który jest ich bieżącym kontekstem wykonania. Nie mają one zasięgu blokowego, więc można uzyskać do nich dostęp spoza bloku, w którym zostały zdefiniowane. Może się tak stać pod warunkiem, że zmienne te nadal znajdują się w zasięgu kontekstu wykonania. Zmienne let
i const
mają jednak zasięg blokowy i nie można uzyskać do nich dostępu spoza bloku. Widać to poniżej:
Do niektórych zmiennych można się odwołać poza ich zasięgiem
Ponadto, gdy deklarujesz zmienną globalną ze słowem kluczowym var, to zostaje ona dołączona do kontekstu globalnego (window
w przeglądarce i global
w Node.js). Nie dzieje się tak w przypadku zmiennych globalnych zadeklarowanych za pomocą let
i const
.
Warto zapamiętać
- Gdy po prostu przypiszesz wartość do zmiennej, bez deklaracji za pomocą słów kluczowych, zmienna ta zostaje utworzona i dołączona do globalnego kontekstu wykonania (window w przeglądarce i global w Node.js). Robienie tego nie jest jednak zalecane, ponieważ znacznie utrudnia to debugowanie.
Zmienne zostają dołączone do kontekstu globalnego
- Zmienne zadeklarowane za pomocą słowa kluczowego var mogą być ponownie zadeklarowane w dowolnym momencie kodu, nawet jeśli są w tym samym kontekście wykonania. Nie dotyczy to zmiennych zdefiniowanych za pomocą słów kluczowych
let
iconst
, ponieważ można je zadeklarować tylko raz w ramach ich zasięgu leksykalnego.
Nie można ponownie zadeklarować zmiennych let i const
Może się pojawić wtedy problem, zwłaszcza, jeśli używasz let
lub const
do deklarowania zmiennej wewnątrz switch.
W obu przypadkach, foo znajduje się w tym samym bloku
Oczywiście można tego uniknąć, używając nawiasów klamrowych wokół case'ów, aby zdefiniować różne bloki, ale prawdopodobnie należałoby to zrefaktoryzować.
W obu przypadkach, foo znajduje się w różnych zasięgach blokowych
- Kolejną kwestią, o której warto pamiętać to fakt, że mimo iż nie można przypisać ponownie wartości do stałej, to nadal jest ona mutowalna. Dobrze można to zilustrować faktem, że jeśli wartością jest obiekt, właściwości obiektu będzie można modyfikować.
Zmienne zdefiniowane za pomocą const nadal można modyfikować