Nauka JavaScript: Block Bindings czyli var, let czy const?

Uczysz się JavaScript i poszukujesz dodatkowych materiałów? W tym artykule znajdziesz informacje na temat sposobu deklarowania zmiennych w JavaScript. Cały kod pisany jest w standardzie ES6. Brzmi ciekawie?

Czego się dzisiaj dowiesz?

W tym artykule postaram się wytłumaczyć jak deklarować zmienne w JavaScript. Kiedy wskazane jest użycie zmiennej var kiedy można użyć let a co daje nam const. Do tego napiszę na temat podstawowych koncepcji wiążących się z deklaracją zmiennych czyli hoisting i block binding.

Seria JavaScript

Blog Bedekodzic.pl traktuje o nauce programowania aplikacji webowych. Jak można o tym pisać bez znajomości JavaScript? No właśnie… Zakładając, że masz już podstawową wiedzę na temat JS’a będę objaśniał zagadnienia związane ze jego współczesnymi wersjami czyli ES6 i w górę!

Wszystkie wpisy z serii można śledzić na moim blogu pod tym LINKIEM

Zmienne lokale i globalne w ES5

Zanim przejdę to aktualnych metod deklaracji zmiennych trochę historii. Pomoże to w zrozumieniu o co generalnie tutaj chodzi.

Rewolucja przyszła wraz z ES6 w 2015 roku. Wcześniej do deklaracji zmiennych używano tylko frazy var. Wszystkie zmienne zdefiniowane w JavaScript miały charakter globalny. Dlaczego? Bo raz zdeklarowane mogły być używane we wszystkich funkcjach. Deklarowało się je w ten sposób:

var number = 1;

console.log(number) // wartość 1

Deklaracja lokalna

Wyjątkiem były zmienne deklarowane wewnątrz konkretnej funkcji. Wtedy były one dostępne tylko wewnątrz tej funkcji były więc zmiennymi lokalnymi.

function local() {
    console.log(name); // (1) Undefined - Zmienna nie została jeszcze zdeklarowana
    var name = 'Lucy';
    console.log(name); //(2) Teraz jest Lucy;
}

local(); // Przy wywołaniu funkcji otrzymamy zdeklarowaną wartość z 2 loga

console.log(name); //(3) Teoretycznie powinien być Error not defined…

Powyższy przykład pokazuje jak deklarować zmienną lokalną. Powinna być ona nie dostępna poza funkcją local ( trzeci console.log ) jednak zmienna name tam jest bo nie wyrzuca mi błędu. Ma po prostu pustą wartość. Bardzo dziwne… Nie powinno tak być. Wygląda na to, że tylko wartość jest lokalna a deklaracja globalna… Przynajmniej w tym wypadku. Idziemy dalej.

Pętla for

Przyjrzyjmy się teraz pętli logicznej for:

for(var i=0; i<5; i++){
};

console.log(i); // wartość 5

Tworzymy w niej zmienną i która teoretycznie powinna być lokalna bo jest zdeklarowana w obrębie bloku kodu for. Mimo to jak wywołujemy ją poza blokiem dostajemy jakąś wartość. Dlaczego tak się dzieje?

Przekazywanie wartości- lokalna vs globalna

Skoro zmienna globalna jest dostępna wszędzie a lokalne tylko wewnątrz funkcji to jak kontrolować ich wartości? Opiszę to w następnym przypadku:

var number = 5;

function check() {
    console.log(number); //(1) undefined bo później używamy var
    number = 6 ;
    console.log(number); //(2) zmiana wartości na 6
    var number = 8;
    console.log(number); //(3) re-deklaracja za pomocą var zmiennej- 8
}

check();

console.log(number); //(4) wynik 5

Powyżej na wstępie deklaruje zmienną globalną number. W funkcji check sprawdzam jak zmienia się wartość zmiennej.

Pierwszy log daje mi wartość number undefined. Dziwne? Trochę tak bo spodziewałbym się wartości 5. Jest tak dlatego bo później zmieniamy tą zmienną na lokalną re-deklarując ją za pomocą var. Potem mamy do czynienia z hoistingiem (o tym później) i dostajemy undefined.

Gdybym nie użył deklaracji var wewnątrz funkcji pierwszy log dał by mi wynik 5.

Drugi log daje mi wartość 6 bo zmienna jest już zdeklarowana (a nawet podwójnie poprzez hoisting). Po prostu zmieniam wartość.

Trzeci log po re-deklaracji daje wartość 8. Bez zaskoczenia.

Czwarty log może zaskoczyć wynikiem bo daje nam 5. Dlaczego nie 8? W związku z tym, że zmieniliśmy zmienną na lokalną to wszelkie operacje wewnątrz danej funkcji nie zmienią wartości pierwotnej, globalnej zmiennej. My to możemy się w tej funkcji produkować a wynikiem na końcu będzie 5 🙂

Wnioski

  1. Jeśli wewnątrz funkcji chcemy zmodyfikować wartość zmiennej globalnej to nie re-definiujmy tej zmiennej przy użyciu słowa var.
  2. Wszelkie operacje na wartościach zmiennych lokalnych wewnątrz funkcji nie są przekazywane poza funkcję.
  3. W niektórych przypadkach zmienne lokalne mogą być przekazywane na zewnątrz bloku z kodem np. funkcji lub pętli logicznej.

No to ładnie… ciekawe co będzie dalej…

Hoisting

Ta nazwa jest dość enigmatyczna i zdecydowanie łatwiej zrozumieć całą koncepcję używając polskiego tłumaczenia- windowanie/podnoszenie. O co chodzi? To taka właściwość języka JavaScript. Wbrew pozorom to dość łatwe.

Jeśli w dowolnym miejscu w obrębie bloku kodu (np. funkcji) zdefiniujemy zmienną za pomocą var to jest ona automatycznie przenoszona na początek bloku. Pamiętacie kiedy w poprzednich przykładach dostawałem wartości undefined? Przykład:

function hoistCheck (){
    console.log(hoistVal); // (1) undefined- powinien być ERROR bo zmiennej jeszcze nie ma... - efekt hoistingu
    var hoistVal = 1;
    console.log(hoistVal); // (2) dopiero teraz jest zdeklarowana
}

hoistCheck ();

Powyżej widać, że pierwszy log daje wartość undefined pomimo tego, że powinien być ERROR. Po prostu hoisting widząc, że zmienna jest zdeklarowana za pomocą var przerzucił ją na początek tylko jeszcze bez wartości… Stąd undefined. Tak działa hoisting czyli przerzuca wszystko wyżej

Teoretycznie powyższy kod wygląda tak:

function hoistCheck (){
    var hoistVal
    hoistVal = 1;
    console.log(hoistVal);
}

hoistCheck ();

Hoisting dotyczy też funkcji tzn. raz zdeklarowane mogą być wywoływane zarówno przed jak i po tej deklaracji.

hoistCheck (); // funkcja jest wywołana przed deklaracja... i działa!

function hoistCheck (){
    console.log(hoistVal);
    var hoistVal = 1;
    console.log(hoistVal);
}

I co dalej?

Jak widać zrozumienie i opanowanie zagadnień związanych z zakresem zmiennych i hoistingiem w JavaScript może przyprawić o ból głowy… Bardzo łatwo popełnić błąd dlatego warto używać tzw. dobrych praktyk czyli np. deklarowanie zmiennych na początku bloku kodu.

Tak czy inaczej wraz z nadejściem ES6 nastąpiła rewolucja czyli wprowadzenie Block Level Binding (pol. dowiązywanie do poziomu bloku) lub inaczej Block Level Scoping. Nie trzeba już się tym wszystkim przejmować…

Block Level Binding

Czyli “dowiązanie do poziomu bloku”. Jaka właściwość kryje się pod powyższym terminem? ES6 poszło śladem innych języków programowania i uregulowało sprawę zakresu zmiennych. W jaki sposób?

Otóż stosując zasadę BLB można zdeklarować zmienną która zawsze będzie obowiązywała tylko i wyłącznie w obrębie danego bloku kodu. Czym jest blok kodu? To kod pomiędzy równorzędnymi klamrami {} (ang. curly braces). Przykład?

hoistCheck ();

function hoistCheck (){ // tu się zaczyna blok kodu
    console.log(hoistVal);
    var hoistVal = 1;
    console.log(hoistVal);
} // tu się kończy blok kodu

Skoro już wiemy czym jest Block Level Binding to jak możemy definiować zmienne które będą działały tylko w obrębie bloku?

Deklaracja let

Robi się to używając frazy let zamiast frazy var. Jak to działa? Przykład:

hoistCheck ();

function hoistCheck (){
    console.log(hoistVal); //(1) ERROR bo zmienna jeszcze nie istnieje
    let hoistVal = 1;
    console.log(hoistVal); //(2) Wartość 1
}

console.log(hoistVal); //(3) ERROR bo zmienna jest lokalna i działa tylko w obrębie bloku kodu

Pierwszy log wyrzuca błąd bo ta zmienna jeszcze nie istnieje. Pokazuje to, że użycie let rozwiązuje problem hoistingu zmiennych w obrębie bloku. Klawo 🙂

Drugi log daje nam pożądaną wartość czyli bez zaskoczenia.

Trzeci log wyrzuca błąd bo taka zmienna nie istnieje. Jakżeby miała istnieć poza funkcją 🙂 Przecież jest lokalna.

Sprawdźmy jeszcze pętlę for:

for(let i=0; i<5; i++){
};

console.log(i); //ERROR, zmienna 'i' nie istnieje!

Zmienna ‘i’ nie istnieje poza pętlą 🙂 Doskonale!

Jak widać użycie deklaracji let znacząco klaruje ogólną sytuację w kodzie. Zmienne są dokładnie tam gdzie powinny być.

let vs var

No dobra skoro to let jest takie użyteczne to co z użyciem var? Do czego się może przydać?

Szczerze mówiąc to moje doświadczenie uniemożliwia mi wysypanie jak kart z rękawa sytuacji w której użycie var będzie bardziej porządane od użycia let. Deklaracja let wprowadza porządki w kodzie. To zawsze pomaga. Wystarczy pilnować odpowiednich bloków.

Drogi czytelniku, jeśli masz jakiś pomysł kiedy użycie var może być korzystne to napisz proszę w komentarzu.

Deklaracja const

No dobra skoro let przynosi tyle zmian to o co chodzi z const? Na pewno pamiętacie z fizyki to wyrażenie 🙂 Cudowne chwile spędzone w szkolnej ławie…

Zmienna const posiada wszystkie nowe i wspomniane wyżej właściwości plus kilka unikalnych których nie ma ani var ani let. Przykład:

const auto = "Fiat";

console.log(auto); //(1) Mamy zdeklarowaną wartość

auto = "Ford"; //(2) ERROR bo nie możemy zmienić pierwotnej wartości

auto += "Ford"; //(3) ERROR bo nie możemy nic dopisać...

const auto = "Audi"; //(4) ERROR bo nie możemy re-deklarować tej zmiennej

Powyższy przykład pokazuje, że nie bardzo możemy modyfikować zmienną const. Przypisane wartości stałe.

Sprawdźmy jak działa w pętli for:

for(const i=0; i<5; i++){
    console.log(i); //ERROR przy drugiej iteracji...
};

Tak jak podejrzewałem… Przy drugiej iteracji pętli program się wykrzacza… Nie możemy przecież nadpisywać zmiennej const

No dobra to czy możemy zmienić cokolwiek w const?

Obiekty const

Okazuje się, że przy użyciu const można dość swobodnie tworzyć obiekty. Należy tylko pamiętać o pewnej kwesti…

Stwórzmy jakiś obiekt:

const city = {
    name: 'Wrocław',
    date: 1214
}

console.log(city);

Mamy obiekt city o dwóch właściwościach tzn. name i date. Czy możemy modyfikować te właściwości? Może w ten sposób:

const city = {
    name: 'Wrocław',
    date: 1214
}

const city = {
    name: 'Warszawa',
    date: 1262
} //ERROR - tak się nie da...

city = {
    name: 'Warszawa',
    date: 1262
} //ERROR - tak też nie...

console.log(city);

Dwa powyższe sposoby nie działają… Niespodzianka? Spróbujmy inaczej:

const city = {
    name: 'Wrocław',
    date: 1214
}

city.name = "Warszawa";

console.log(city);

Teraz zadziałało 🙂 Po prostu trzeba się oddzielnie dobierać do każdej właściwości. A teraz pytanie czy możemy dodawać właściwości do obiektu?

const city = {
    name: 'Wrocław',
    date: 1214
}

city.size = 'big';

console.log(city); // Nowa właściwość jest dodana!

Okazuje się, że możemy 🙂 Nie jest więc tak źle z tym const!

Co dalej?

Jeśli chodzi o ten wpis to byłoby na tyle moich wywodów. Ameryki nie odkryłem, Marsa też jeszcze nie zdobyłem 🙂

Zachęcam do zagłębiania swojej wiedzy u różnych źródeł. Znalazłem bardzo fajnie napisany artykuł nawiązujący do kwestii które tu poruszałem:

https://scotch.io/tutorials/understanding-hoisting-in-javascript#order-of-precedence

Poza tym internet jest pełen wiedzy. Bo kto nie szuka, to nie znajdzie 🙂

Podsumowanie

Mam nadzieję, że dzisiejszy wpis pomógł Ci zrozumieć zagadnienia związane z deklarowaniem zmiennych w JavaScript. To bardzo przydatna wiedza która ułatwia zagłębianie się w kolejne aspekty tego kwitnącego języka. A co 🙂 Przecież teraz mamy kwiecień- wiosna idzie 🙂

Print Friendly, PDF & Email

Bartek Cis

Piszę dla was tego bloga bo lubię aplikacje internetowe. Mogę je projektować, kodować a potem o nich pisać czując dreszczyk ekscytacji za każdym razem gdy trafię na coś nowego. Bo uczymy się całe życie. Prawda?