Z racji #zostańwdomu postanowiłem odświeżyć pewne zagadnienia związane z programowaniem. Jest to pierwszy wpis z serii na tematy związane z JavaScriptem, Reactem, html oraz css.
Na początek, w formie rozgrzewki, na warsztat wziąłem temat deklaracji zmiennych w JavaScripcie. Więc … zaczynamy …
Zmienne są to opakowania wewnątrz których przechowujemy dane takie jak liczby, tekst oraz bardziej złożone obiekty. Dokładniej to rozpiszę innym razem 🙂
Po co nam zmienne? Pokaże na przykładzie prostego zadania.
Zadanie: Policz pola trzech kół o promieniach 3, 5 oraz 7.
//Math.pow() -> funkcja licząca potęgę liczby console.log(3.1415926535897 * Math.pow(3,2)) //28.2743338823073 console.log(3.1415926535897 * Math.pow(5,2)) //78.5398163397425 console.log(3.1415926535897 * Math.pow(7,2)) //153.9380400258953
Za każdym razem liczba Pi wpisana jest ręcznie (na potrzeby przykładu zapominam, że Pi jest dostępne w bibliotece Math ). Ponadto czytelność powyższego kodu nie jest najlepsza.
Wprowadzę teraz zmienne:
const pi = 3.1415926535897 //deklaracja zmiennej pi const r1 = 3 // promień pierwszego koła const r2 = 5 // promień drugiego koła const r3 = 7// promień trzeciego koła console.log(pi * Math.pow(r1,2)) //28.2743338823073 console.log(pi * Math.pow(r2,2)) //78.5398163397425 console.log(pi * Math.pow(r3,2)) //153.9380400258953
Po pierwsze kod jest bardziej czytelny, wiadomo skąd się biorą poszczególne wartości, po drugie o wiele łatwiej manipulować zmiennymi, np zmieniając dokładność Pi. Oczywiście, mógłbym wprowadzić funkcję aby kod był jeszcze lepszy.
W JavaScript deklaracja zmiennych odbywała się za pomocą słowa var. Naturalnie w kodzie zdarza się sytuacja, że chcemy aby jakaś zmienna była stała, niezmienna. W przypadku korzystania z deklaracji var nie było wiadomo, co jest zmienną a co powinno być stałą, bo de facto wszystko było zmienną. Programiści wypracowali metodę rozróżniania zmiennych od stałych. Otóż stałe zapisywano dużymi literami, dzięki temu w dalszej części kodu wiadomo było czy daną wartość można nadpisać. Lecz była to tylko konwencja nazewnictwa i nie niosła za sobą żadnych realnych ograniczeń w kodzie. Inni programiści widząc taką zmienną zapisaną dużymi literami wiedzieli, że dobrze byłoby nie nadpisywać tej zmiennej. Wraz z pojawieniem się standardu ES6 wprowadzono dwa nowe słowa kluczowe służące do deklaracji zmiennych: let oraz const. Już samo nazewnictwo rozróżnia ich przeznaczenie. let / const wnoszą pewne zmiany w stosunku do var.
Czyli po kolei:
var name = 'John' //deklaracja zmiennej var SURNAME = 'Doe' // deklaracja "stałej", ale tak naprawde zniennej let c = 0 //deklaracja zmiennej const b = 0 //deklaracja stałej
Spróbuję zmienić wartości,
var name = 'John' //deklaracja zmiennej name = 'Alfred'; console.log(name); // name zwraca Alfred var SURNAME = 'Doe' // deklaracja "stałej" SURNAME = 'Smith' console.log(SURNAME) // SURNAME zwraca Smith. let a = 0 //deklaracja zmiennej a = 10; console.log(a) // a zwraca 10 const b = 0 //deklaracja stałej b=5 // Error. Nie można nadpisać b
jak widać w powyższym przykładzie “SURNAME” mogłem spokojnie nadpisać. Tak jak pisałem wcześniej, zadeklarowanie “stałej” jako var pisane dużymi literami, była to tylko konwencja nazewnictwa, w praktyce była to zwykła zmienna którą można było nadpisać. Za to “b” zadeklarowana jak const nie może być nadpisana.
Idźmy dalej. Jak widać zmiennej zadeklarowanej jako const nie mogę nadpisać, ale co jeżeli za pomocą const zadeklarujemy typ złożony taki jak tablice lub obiekt. W takim przypadku mogę zmieniać składowe elementu lub jego wartości. Niedozwolone jest tylko przypisanie na nowo do const.
Tabela:
const names = [] // deklaracja tabeli names.push('Alan','Brian') // dodanie elementów do tabeli console.log(names) // ['Alan', 'Brian'] names[0] = 'Jurgen' // zmiana elementu w tabeli console.log(names) // ['Jurgen'] names.length = 0 // zmiana właściwości tabeli console.log(names) // [] names = ['Alan', 'Jurgen'] // przypisanie całej tabeli. Error!
Obiekt:
const user = { name: 'Jurgen' } //deklaracja obiektu user.name = 'Alan' //zmiana wartości składowej obiektu console.log(user) //{ name: "Alan" } user.surname = 'Doe' // dodanie składowej obiektu console.log(user) //{ name: "Alan", surname:"Doe" } user = {name: 'Brian'} // przypisanie całego obiektu. Error!
Warto zwrócić uwagę na kwestię ponownej deklaracji zmiennej. Otóż var pozwalał na taki zabieg, natomiast let już nie dopuszcza takiej sytuacji
var city = "Rome"; var city = "London"; console.log(city) // London let country = "Poland"; let country = "Italia"; // Error. Identifier 'country' has already been declared
Kolejną, bardzo ważną różnicą pomiędzy var a let / const jest hoisting zmiennych. W skrócie Hoisting jest to cecha JavaScriptu która wynosi deklaracje zmiennej na początek bloku kodu. Istotne jest, że wynoszona była tylko deklaracja a nie wartość.
Deklaracja zmiennej bez przypisania wartości:
const name //deklaracja zmiennej, zmienna nie posiada wartości console.log(name) //undefined
Deklaracja zmiennej z przypisaniem wartości:
const name = 'Jurgen' //deklaracja zmiennej i przypisanie wartości console.log(name) //Jurgen
Hoisting wynosi na początek tylko deklaracje zmiennej:
console.log(name) // undefined. var name = 'Jurgen';
poprzez działanie hoistingu ten fragment kodu był interpretowany w następujący sposób:
var name; // Hoisting wyniósł deklaracje zmiennej name na sam początek kodu console.log(a) // undefined name = 'Jurgen';
Dla let taki zapis nie przejdzie. JavaScript wymaga od programisty myślenia i wymusza deklaracje zmiennej przed jej użyciem. Co w sumie jest sensowne 🙂
Czyli poniższy zapis jest błędny:
console.log(city) // Error!. Cannot access 'city' before initialization let city = "Rome"
Wymagane jest przynajmniej zadeklarowanie zmiennej przed jej użyciem:
let country; console.log(country) // undefined, country zadeklarowane, ale nie ma przypisanej wartości country = "Italy"; console.log(country) // Italy
Ciekawym aspektem jest też kwestia kiedy zmienne są parametrami funkcji. Gdy zmienne są zadeklarowane jako var to hoisting wyciąga ich deklaracje na początek i do funkcji trafiają jako undefined. Funkcja się wywołuje bez problemu, po prostu ma puste parametry:
function showPlace(a,b){ console.log(a,b)//undefined undefined } showPlace(city,country) var city = "Rome"; var country = "Italy"
Natomiast gdy zmienne są zadeklarowane jako let, a wiemy już że hoisting tutaj nie zadziała w ten sposób, więc …
function showPlace(a,b){ console.log(a,b) } showPlace(city,country) // Error! Cannot access 'city' before initialization let city = "Rome"; let country = "Italy"
Wystąpił błąd na poziomie wywołania funkcji. Funkcja w ogóle się nie odpala. Jak widać jest zasadnicza różnica w działaniu.
Wprowadzenie let / const zmieniło trochę zasięg zmiennych. Var miało zasięg funkcyjny, czyli ich zasięg określało ciało funkcji. Natomiast let / const ma zasięg blokowy, tj od klamry do klamry. To znaczy że mamy lepsza kontrolę nad zmiennymi lokalnymi.
var city = "Rome" //zmianna globalna { var city = "London" // zmienna lokalna console.log(city) // London } console.log(city) // London
Natomiast jeżeli wprowadzę funkcję:
var city = "Rome" //zmianna globalna function showCity(){ var city = "London" // zmienna lokalna console.log(city) // London } showCity() console.log(city) // Rome
Wrócę do pierwszego przykładu, tym razem z użyciem let zamiast var:
let city = "Rome" //zmianna globalna { let city = "London" // zmienna lokalna console.log(city) // London } console.log(city) // Rome
let city = “London” jest widoczna tylko w obszarze klamer.
Inny przykład. Dla var:
var condition = true if (condition) { var city = "Rome"; } console.log(city); // Rome, zmienna city widoczna jest poza if'em
Natomiast dla let:
let condition = true if (condition) { let city = "Rome"; } console.log(city) //Error! city is not defined
I ostatni przykład, w nim widać różnicę w zasięgu blokowym a funkcyjnym.
{ let city = 'Rome'; var country = 'Italia'; } console.log(city); // Error! city is not defined console.log(country); // Italia
Let jest niewidoczna poza klamrami, natomiast same klamry nie ograniczają widoczności var. Dopiero zamknięcie var w funkcji ogranicza jego widoczność.
(function() { let city = 'Rome'; var country = 'Italia'; })(); console.log(city); // Error! city is not defined console.log(country); // Error! country is not defined
Jeszcze jeden przykład pokazujący ciekawą rzecz:
function counter(){ for (var i=0; i<10; i++) { console.log(i); } console.log(i); //10 } counter()
Zmienna var i jest widoczna po skończonej pętli for, gdyż jest w obrębie ciała funkcji. Natomiast jeżeli w tym fragmencie kodu zmienną zadeklaruje jak let:
function counter(){ for (let i=0; i<10; i++) { console.log(i); } console.log(i); //Error!. i is not defined } counter()
Zmienna let i jest widoczna tylko w obrębie pętli for.
Domknięciem tego wpisu będzie Closures, czyli … domknięcia 🙂 Domknięcie jest to obszar stworzony przez funkcje które “zamyka” w nim zmienne i funkcje oraz odgradza je od reszty kodu. Czyli inaczej closures to taki płot na łące który odgradza znajdujące się wewnątrz zwierzęta od reszty łąki (taką alegorie znalazłem w necie i bardzo mi się spodobała 🙂 ), logiczne i proste. To teraz jak się ma to do programowania i zmiennych. De facto domknięcie były już zastosowane w przykładach powyżej. Działa to tak samo dla var oraz dla let.
let city = "Rome" //zmianna globalna function showCity(){ //funkcja tworzy swoje domknięcie let city = "London" // zmienna lokalna function printCity(){ console.log(city) // London } return printCity() } // poza funkcją jej zmienne oraz funkcje nie są dostępne showCity() console.log(city) // Rome
Funkcja może także mieć dostęp do zmiennej wyższego poziomu:
let city = "Rome" //zmianna globalna function showCity(){ //funkcja tworzy swoje domknięcie city = "London" // zmiana globalnej zmiennej function printCity(){ console.log(city) // London } return printCity() } // poza funkcją jej zmienne oraz funkcje nie są dostępne showCity() console.log(city) // London
Oczywiście może wystąpić kombinacja zmiennych lokalnych oraz globalnych:
let city = "Rome" //zmianna globalna function showCity(){ //funkcja tworzy swoje domknięcie city = "London" // zmiana globalnej zmiennej która jest dostępna wewnątrz dokmknięcia let country = "Italy" // zmienna lokalna function printCity(){ console.log(city,country) // London, Italy } return printCity() } // poza funkcją jej zmienne oraz funkcje nie są dostępne showCity() console.log(city) // London console.log(country) // Error! country is not defined (jest to zmienna lokalna widoczna tylko dla funkcji showCity)
Zmienne pozwalają przechowywać nam dane. Wprowadzenie let i const w znaczący sposób usystemizowało JS’a. Mam jawnie rozdzielone na zmienne oraz stałe. W obrębie bloku nie możemy deklarować zmiennych o tej samej nazwie. Zmiana zasięgu zmiennych pozwala na przyjemniejsze (IMO) pisanie kodu. W pewien sposób pozytywnym jest także ukrócenie hoistingu dla zmiennych i wymaganie od programisty pisania kodu który jest czytelniejszy oraz ma dobrą strukturę (plus to, że zniknie jedno standardowe pytanie rekrutacyjne 😉 ). Zalecam stosowanie const i let zamiast var.
I to tyle w dzisiejszym wpisie, zachęcam do zostawienia / przesłania feedbacku. Dziękuję za uwagę.
Łukasz