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ą adekwatnoś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ę, iż zmienne deklaruje się w nim dzięki 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, iż 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, iż 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 iż 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 dzięki 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 dzięki 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żeli 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żeli chodzi o zasięg. Zmienna stworzona dzięki 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 iż 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” dzięki nawiasów klamrowych, aby zilustrować przykład. W realnym kodzie dzięki zasięgowi blokowemu, który mają zmienne stworzone dzięki 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, iż 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żeli 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 dzięki 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, iż na pewno nikt nie zmieni ich wartości. Dodatkowo, zmienna tworzona dzięki 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ć, iż to nie jest tak, iż zmienne stworzone dzięki 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 dzięki const będzie tablicą, przez cały czas 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, iż chcemy stworzyć zmienną tylko do odczytu, nie do nadpisywania. jeżeli 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 w tej chwili bardzo powszechne i zachęcam Was do korzystania z nich w Waszym kodzie. Oczywiście, pewnie zdarzą się przypadki, iż 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 dzięki let i const, całkowicie wykluczając var. Argumentem jest uniknięcie tworzenia zmiennych globalnych.
To tyle na dziś! Mam nadzieję, iż to krótkie omówienie sposobu deklarowania zmiennych w ES6 będzie dla Was przydatne. Ruszajcie teraz do kodu i korzystajcie z dobrodziejstw ES6!