Jak deklarować zmienne zgodnie z zasadami ES6? Na czym polegają instrukcje let i const? O tym w dzisiejszym wpisie, drugim poruszającym zagadnienia z ES6, które warto znać.
Poprzedni wpis z serii omawiał zagadnienia związane ze zwięzłą deklaracją właściwości obiektu oraz z destrukturyzacją obiektów, znajdziecie go tutaj. Oba wpisy powstały na podstawie książki Nowoczesny JavaScript autorstwa Nicolasa Bevacqua.
Jeśli macie już troszkę do czynienia z JavaScriptem, pewnie zdajecie sobie sprawę, że zmienne deklaruje się w nim za pomocą instrukcji var
(jest to skrót od angielskiego słówka variable). W uproszczeniu – zmienne pozwalają nam przechowywać fragmenty danych tak, by móc ich używać w dalszej części kodu. Załóżmy, że chcemy stworzyć zmienną, która będzie przechowywała nasze imię. Zrobimy to w ten sposób:
var name = "Joanna";
Kiedy powyższą zmienną chciałabym wylogować w konsoli, aby zobaczyć jej treść, pozwoli mi na to następujący kod:
console.log(name); // wypisze w konsoli "Joanna"
Mojej zmiennej mogę też użyć przy wywołaniu funkcji czy posługiwać się nią pisząc inny kod. Wydaje się super proste i nieskomplikowane. Po co więc powstały let
i const
, czy naprawdę var
nie wystarczy?
Mówiąc o zmiennych w JS, trzeba wspomnieć o zjawisku hoistingu. Hoisiting powoduje, że deklaracje zmiennych przenoszone są na początek kodu. Żeby to zobrazować, spójrzcie na poniższy kod:
city = "Poznań"; console.log(city); // w konsoli zobaczymy "Poznań" var city;
Mimo że powyższy kod najpierw chce wypisać w konsoli zmienną, a dopiero potem ją deklaruje, kod nie wyrzuci błędu. Dzięki hoistingowi deklaracje jest przenoszona na początek wykonywanego skryptu. Wydaje się to całkiem fajną sprawą, ale w długim i złożonym kodzie może być dość problematycznie. Kiedy zaczniemy odwoływać się do zmiennych, które deklarujemy dopiero później, kod może być mniej czytelny.
Ale co ma do tego let
? Bardzo wiele! Let
jest alternatywą dla deklarowania zmiennych za pomocą var
. Aby stworzyć zmienną w JS, możemy alternatywnie użyć instrukcji let
zamiast var
. Na pierwszy rzut oka efekt będzie taki sam – uzyskamy zmienną, której możemy użyć w kodzie. Jednak inaczej będzie wyglądało zjawisko hoistingu, ponieważ powstanie tzw. tymczasowa strefa martwa. O co w niej chodzi? Kiedy deklarujemy zmienną przy użyciu let
, poniższy kod nie pokaże nam wartości zmiennej name
:
function printName() { console.log(name); }; printName(); // w konsoli zobaczymy "Uncaught ReferenceError: name is not defined" let name = "Joanna";
Dlaczego tak się dzieje? Zmienne zaklerowane za pomocą let
pozostają niedostępne dopóki skrypt nie dojdzie do faktycznego momentu ich deklaracji. Dzięki temu unikamy prób dostępu do zmiennej zanim zostanie ona zdeklarowana.
To jednak nie wszystko, jeśli chodzi o użycie let
! Drugim dużym plusem jest zasięg zmiennych stworzonych w ten sposób. Jak pisze Bevacqua we wstępnie do rozdziału o deklaracji zmiennych: JavaScript ma od zawsze skomplikowany zestaw reguł dotyczących ustalania zasięgu, co doprowadza wielu programistów do szaleństwa, kiedy próbują zrozumieć, jak w tym języku działają zmienne. I użycie let
ma trochę ułatwić sprawę, jeśli chodzi o zasięg. Zmienna stworzona za pomocą let
będzie miała bowiem zasięg blokowy, a nie domyślny zasięg leksykalny. Spójrzmy na poniższe przykłady, aby to zrozumieć.
{{{ var myVariable = "abc" }}}; console.log(myVariable); // wypisze w konsoli "abc"
Mimo że w powyższym kodzie tworzę trzy bloki, a zmienna myVariable
powstaje dopiero w zagnieżdżony trzecim bloku, mam do niej dostęp także z zewnątrz. Tak właśnie działa zasięg leksykalny. Zmienna nie byłaby dostępna dopiero, gdybym np. chciała ją wywołać w innej funkcji, tj. gdybym zamknęła całość np. w funkcji. O zasięgu leksykanym więcej polecam poczytać tutaj.
Kiedy chcielibyśmy wykonać powyższy kod zamieniając w nim var
na let
, nie zyskamy już dostępu do zmiennej myVariable
. Zgodnie z zasadami zasięgu blokowego, możemy mieć do niej dostęp tylko wewnątrz bloku, w którym zmienna została zdeklarowana.
{{{ let myVariable = "abc" console.log(myVariable); // wypisze w konsoli "abc" }}}; console.log(myVariable); // pojawi się błąd: "myVariable is not defined"
Oczywiście powyżej tworzę bloki trochę “sztucznie” za pomocą nawiasów klamrowych, aby zilustrować przykład. W realnym kodzie dzięki zasięgowi blokowemu, który mają zmienne stworzone za pomocą let
możemy używać tej instrukcji na przykład w pętlach. Dzięki temu z każdą iteracją pętli będziemy tworzyć nowe wiązanie, a zmienne będą dostępne faktycznie tylko tam, gdzie chcemy, by były użyte. Uchroni nas to przed nadpisywaniem zmiennych, co mogłoby się zdarzyć, gdy mamy zmienne o tej samej nazwie. Dzięki użyciu let
możemy być spokojni, że zmienna ograniczona jest do danego bloku i w jego obrębie możemy na niej działać.
ES6 umożliwia nam jeszcze jeden sposób deklarowania zmiennych – jest to instrukcja const
. Tak jak let
i var
są dla siebie alternatywne i wprowadzają jedynie małe różnice, tak const
to trochę inna sprawa. Ta instrukcja bowiem pozwala nam stworzyć tzw. stałe, czyli zmienne, których wartości nie będziemy mogli nadpisać. Jeśli chodzi o zasięg czy tymczasową strefę martwą, const
zachowuje się tak samo jak let
. Popatrzmy jednak na poniższy kod:
let a = 1; console.log(a) // wypisze w konsoli 1 a = 2; console.log(a) // wypisze w konsoli 2 const b = 3; console.log(b) // wypisze w konsoli 3 b = 4 // wyrzuci błąd: "b is ready-only"
Jak widać wyraźnie, do zmiennych zadeklarowanych za pomocą const
nie można później nic przypisać. Instrukcja ta będzie więc nam służyła do stworzenia zmiennych, które mają mieć “stałą” treść i chcemy być pewni, że na pewno nikt nie zmieni ich wartości. Dodatkowo, zmienna tworzona za pomocą const
musi zostać zainicjalizowana, czyli wymaga ona wartości początkowej. Obrazuje to poniższy przykład:
let a; // nie wyrzuca błędu a = 123; const b; // wyrzuci błąd: "unexpected token" - program oczekuje = zamiast ;
Warto jednak wspomnieć, że to nie jest tak, że zmienne stworzone za pomocą const
są zupełnie niezmienne. Oczywiście, nie możemy ich nadpisać, jak w przypadku liczb z powyższych przykładów. Kiedy jednak zmienna, którą stworzyliśmy za pomocą const
będzie tablicą, nadal będziemy mogli wykonać na niej akcje modyfikujące tę tablicę.
const array1 = [1,2,3]; array1 = [4,5,6]; // wyrzuci błąd: "array1 is read-only" array1.push(4); // nie wyrzuca błędu, dodajemy element do tablicy console.log(array1) // wypisze tablicę: [1,2,3,4];
Jak widać, deklaracja const
zapobiega zmianie referencji powiązanej ze zmienną, a nie samej zmiennej. Możemy więc naszą tablicę modyfikować.
Używanie const
pozwala nam uniknąć wielu nieporządnych zdarzeń. Kiedy korzystamy z const
, w większości przypadków jest to znak, że chcemy stworzyć zmienną tylko do odczytu, nie do nadpisywania. Jeśli wartość naszej zmiennej ma być zmienna, użyjemy wtedy let
do jej zdeklarowania. To wprowadza do kodu pewną konwencję i porządek.
Użycie let
i const
jest obecnie bardzo powszechne i zachęcam Was do korzystania z nich w Waszym kodzie. Oczywiście, pewnie zdarzą się przypadki, że uzasadnione będzie dla Was skorzystanie z var
np. ze względu na zasięgi. Jednak let
i const
zapewniają w kodzie większy porządek. Poparciem tych słów niech będą wytyczne ze styleguide’a airbnb dotyczące zmiennych – polecają oni zawsze deklarowanie zmiennych za pomocą let
i const
, całkowicie wykluczając var
. Argumentem jest uniknięcie tworzenia zmiennych globalnych.
To tyle na dziś! Mam nadzieję, że to krótkie omówienie sposobu deklarowania zmiennych w ES6 będzie dla Was przydatne. Ruszajcie teraz do kodu i korzystajcie z dobrodziejstw ES6! 🙂
Hej,
Ciekawy wpis, fajnie zrozumiale obrazuje zagadnienie. Mam natomiast jedną drobną uwagę:
console.log(city);
var city = “Poznań”; // w konsoli zobaczymy “Poznań”
W konsoli w takim przypadku zobaczymy “undefined”, ponieważ hoisting przenosi tylko samą deklarację, a przypisanie wartości pozostaje w miejscu oryginalnym.
“`javascript
console.log(city);
var city = “Poznań”; // w konsoli zobaczymy “Poznań”
“`
Nie do końca, w konsoli zostanie wyświetlone “undefined”. Hoisting polega na przenoszeniu na początek kodu wyłącznie deklaracji zmiennych, nie zaś – przypisania im wartości.
Dzięki za zwrócenie uwagi, już poprawiłam! Chodzi oczywiście o przenoszenie deklaracji na początek, tak jak piszesz. Teraz przykłady są już odpowiednie 🙂
Tak, masz rację, już poprawiłam. Dzięki za wyłapanie błędu 🙂
Hej, ciekawy artykuł pozwalający usystematyzować wiedzę.
Wkradł Ci się jednak chyba mały błąd:
“function printName() {
console.log(name);
};
printName(); // w konsoli zobaczymy “undefined”
let name = “Joanna”; ”
Tutaj w konsoli nie będzie “undefined”, tylko “Uncaught ReferenceError: name is not defined”.
“Undefined” byłoby w przypadku użycia var.
Pozdrawiam
Tak, zgadza się, dzięki za wyłapanie 🙂 Już poprawione