Zmienne i stałe

Zmienne to coś w rodzaju "pudełek", w których pod nazwami możemy przechowywać różne rzeczy takie jak liczby, teksty, obiekty itp.

Deklarowanie zmiennych

Aby zadeklarować zmienną, powinniśmy posłużyć się jednym ze słów kluczowych.
Do stworzenia zmiennej w JavaScript przez lata używało się słowa kluczowego var:


var txt = "Ala ma kota";
var nr = 20;
var url = "kontakt.html";

console.log(txt);
console.log(nr);
console.log(url);

JavaScript ewoluuje. Wraz z pojawieniem się ES6 (ECMAScript 2015) wprowadzono nowe sposoby deklaracji zmiennych za pomocą słów kluczowych let i const. Słowo let oznacza zmienną, natomiast const stałą (taką, do której po ustawieniu wartości, nie możemy przypisać nowej):


let txt = "Przykładowy tekst"; //zmienna
txt = "Inny tekst"; //zmieniamy wartość


const nr = 102; //stała
nr = 103; //błąd - nie można zmieniać stałej

W dzisiejszych czasach mimo tego, że w internecie spotkamy miliony skryptów wykorzystujących var, wielu programistów zaleca stosować już const i let. Dzięki nim nasze skrypty stają się nie tylko bardziej optymalne pod względem zarządzania pamięcią, ale i potencjalnie unikamy niektórych problematycznych sytuacji, które występowały przy var (a o których dowiesz się w dalszej części kursu). Przy czym demonizowanie var też nie jest dobre...

Ja w tym kursie będę starał się trzymać nowego zapisu.

Przeglądając Internet zapewne natrafisz na wiele przykładów skryptów, gdzie autorzy tworzą zmienne z pominięciem słów kluczowych var/let/const np:


    nr = 200;
    console.log(nr); //200
    

Jest to błąd, i nigdy tak nie rób. W kolejnych rozdziałach dowiesz się czemu ( 1, 2, 3, 4 ).

Po co stosować zmienne?

Po co w ogóle stosować zmienne? Rozpatrzmy prosty przykład dodawania liczb:


console.log(6 + 5 + 1);
console.log(4 + 5 + 2);
console.log(1 * 5 + 5 - 2);
console.log(8 + (5 * 10) + 5 + 9 + (5 * 10) + 5);
...
...

Przypuśćmy, że teraz chcielibyśmy zmienić cyfrę 5 tak, aby równanie miało postać 9 + 4 + 1.

Dla tak małego skryptu zmiana tej cyfry nie jest wielkim problemem. Co by się jednak stało gdybyśmy takich równań w naszym przykładzie mieli na przykład tysiąc lub nawet sto tysięcy?
I tu właśnie przychodzą nam z pomocą zmienne:


const nr = 4;

console.log(6 + nr + 1);
console.log(4 + nr + 2);
console.log(1 * nr + nr - 2);
console.log(7 + (nr * 10) + nr + 9 + (nr * 10) + nr);

Pod zmienne możemy podstawiać nie tylko liczby, ale teksty, funkcje, obiekty, tablice - czyli praktycznie dowolne rzeczy, na których będziemy pracować w naszych kodach:


const btn = document.querySelector(".button"); //pod zmienną wstawiam pobrany button

btn.classList.add("btn-primary");
btn.setAttribute("title", "Kliknij mnie");
btn.innerText = "Kliknij mnie!"

Nie bój się tworzyć zmiennych.

Spójrz na poniższy przykład generujący losową liczbę:


const random = Math.floor(Math.random() * (max-min+1) + min);
console.log(random);

Widzisz te min i max? To zmienne. Zamiast je ręcznie zamieniać na odpowiednie liczby, o wiele lepiej stworzyć po prostu dodatkowe zmienne.


const min = 1;
const max = 15;
const random = Math.floor(Math.random()*(max-min+1)+min);
console.log(random);

Nazewnictwo zmiennych

Nazwy zmiennych i stałych które deklarujemy nie mogą być byle jakie. Istnieją pewne zasady których musimy się trzymać. I tak:

  • wielkość liter ma znaczenie. Zmienna myTXT to nie to samo co mytxt
  • nazwa zmiennej nie może zaczynać się od cyfry,
  • nazwa zmiennej nie może zawierać spacji, kropki, przecinka ani myślnika (można natomiast używać podkreślenia),
  • nazwą zmiennej nie może być słowo kluczowe zarezerwowane przez JavaScript

Po prostu nie zaczynaj nazw od cyfr i pisz nazwy zmiennych po angielsku. Bardzo częstą praktyką jest stosowanie zapisu camelCase, czyli np. veryImportantThing, ale wiele osób stosuje też zapis z podłogą czyli very_important_thing.

Jest jednak ważniejsza sprawa, którą zapamiętaj.
Nazywaj swoje zmienne tak, by dało się zrozumieć do czego się odnoszą. Zmienna o nazwie elementsCount jest bardziej czytelna, niż xxx. W tym kursie czasami będę korzystał z mało czytelnych zmiennych (np. a), ale wynika to tylko z tego, że kod często jest bardzo, bardzo krótki, a i jestem leniem.

Dla ciekawskich - nazwy zmiennych mogą być bardzo różne. Bardzo, bardzo różne. Nie jest to oczywiście zalecane i trzeba to traktować jako ciekawostkę.


//dobre nazwy
const myAge = 10;
const my_age = 10;

//błędne nazwy
const my-age = 10;
const 2myAge = 10;
const my age  = 10;
const mójWiek = 10;

const

Jedną z głównych bolączek var od zawsze było to, że za pomocą tego słowa można tworzyć tylko zmienne. Oznacza to, że w każdym momencie mogę później taką zmienną zmienić:


var key = "21389123718398";

...

key = 20;

W celu uniknięcia błędów część programistów stałe nazywa w całości dużymi literami. Dzięki temu osoba pisząca kod wiedziała czy powinna czy nie powinna zmieniać danej zmiennej.


var KEY = "21389123718398";

...konwencja ta jest nadal przez wielu programistów stosowana.

Wraz z wprowadzeniem nowych słów let/const dostaliśmy rozróżnienie na zmienne i stałe.
Zmienne deklarowane za pomocą let/var można w przyszłości zmienić - czyli podstawić im nową wartość. Zmiennym stworzonym za pomocą const nie jesteśmy w stanie podstawić nowej wartości.


var text = "ala";
text = "ala ma kota"; // wszystko ok, bo var to zmienna

let a = 0;
a = 10; //wszystko ok, bo let

const b = 0;
b = 10; //błąd - do stałej nie możemy przypisać nowej wartości

const name = "Ala";
name = "Monika"; //błąd

Ale teraz uwaga! Przy const chodzi o podstawienie nowych wartości pod daną zmienną, a nie zmianę składowych tych rzeczy (w przypadku obiektów). W praktyce oznacza to, że dla raz stworzonej stałej const, nie możemy ponownie użyć znaku równości:


const apiUrl = "https://jsonplaceholder.typicode.com";
apiUrl = "http://kursjs.pl" //błąd - podstawiliśmy nową rzecz!

const tab = [1,2,3];
tab[3] = 4; //nie ma błędu, bo tylko zmieniłem składową obiektu (tablica to obiekt)

const tab = [1,2,3];
tab = [1,2,3,4]; //błąd - podstawiłem zupełnie nową tablicę

const tab = [1,2,3];
tab = [1,2,3]; //błąd - podstawiłem zupełnie nową tablicę, nie ważne, że podobnie wygląda

const currentUser = {name: "Piotr", age: 18}
currentUser.age = 20; //nie ma błędu - zmieniłem tylko składową

currentUser = {name: "Piotr", age: 20} //błąd - bo podstawiłem totalnie nowy obiekt

const name = "marcin";
name = name.toUpperCase(); //błąd bo podstawiłem nową wartość

const tab2 = [];
tab2.push(1, 2, 3); //wstawiam 3 elementy
tab2.length = 0; //czyszczę tablicę - zmieniam tylko właściwość tego obiektu
tab2.push(1, 2, 3); //znowu wstawiam itp...

tab2 = []; //błąd bo podstawiłem zupełnie nową wartość

Dlaczego tak się dzieje?
W rozdziale od typach danych podzieliliśmy je na dwie grupy.
Gdy przypisywałem wartość do typu prymitywnego, robił się jej pojedynczy egzemplarz. Dwie oddzielne zmienne mimo, że miały tą samą wartość były oddzielnymi bytami.
Przy obiektach jest inaczej - tutaj działa tak zwana referencja. Zmienne pod które podstawiliśmy jakiś obiekt tylko wskazują na miejsce w pamięci, gdzie dany obiekt się znajduje.
Jeżeli w naszym skrypcie coś zmienimy w takim obiekcie, nie zmieniamy tego na co wskazuje dana zmienna.

Podsumowując. Dla stałych nie możemy na nowo przypisywać całkowicie nowych wartości za pomocą znaku równości. Ale jeżeli stała wskazuje na obiekt, spokojnie możemy w nim mieszać...

Czemu o tym piszę? Bo bardzo często w tym kursie będziesz widział takie zapisy jak poniżej:


const tab = [1,2,3,4];
tab.push(5); //dodaje do tablicy element

I pojawiło by się zmieszanie - co to za stała, skoro możemy ją zmieniać. No ale to jak z nami. Jutro pójdę do fryzjera. Zmienię uczesanie. Ale to dalej ja. Więc stała "Marcin" nie wyrzuci błędu.

Tutaj także kolejna mała uwaga. Mówi się, że w skryptach używających let/const 99% zmiennych powinna być zadeklarowana za pomocą const. Faktycznie coś w tym jest. Większość operacji jakie będziemy robić w tym kursie będzie modyfikować dane obiekty, a nie podstawiać im zupełnie nowe wartości. Ale zdarzać się będą też wyjątki - np. liczniki w pętlach czy zmienne "przełączniki".

Różnice między var a let/const

Deklaracje let/const nie tylko wprowadziły stałe, ale także kilka różnic w stosunku do var.

Pierwsza i najważniejsza różnica między let/const a var to zasięg zmiennych.

W przypadku let/const zmienne mają zasięg blokowy, co w skrócie oznacza "od klamry do klamry":


let a = 20; //zmienna globalna

{
    let a = 30; //zmienna lokalna
    console.log(a); //30
}

console.log(a);    //20

{
    let a = "Ala";
    console.log(a); //Ala
}
{
    console.log(a); //error - nie ma takiej zmiennej
}
{
    let a = "Ola"; //zmienna lokalna w tym bloku
    console.log(a); //Ola
}
console.log(a); //error: a is not defined

for (let i=0; i<10; i++) {
    console.log(i);
}

console.log(i); //error: i is not defined

{
    let nr = 102;
    console.log(nr); //102
}
{
    console.log(nr); //błąd: nr is not defined
}
console.log(nr); //błąd: nr is not defined

Zmienne deklarowane za pomocą var mają natomiast zasięg funkcyjny, czyli ich zasięg określa ciało funkcji.


var a = 20; //zmienna globalna

function test() {
    var a = 30; //zmienna lokalna
    console.log(a); //30
}
test();

console.log(a);    //20

W nielicznych sytuacjach może to powodować niezamierzone działanie kodu.


if (true) {
    var myVar = 20;
}

console.log(myVar); //20

for (var i=0; i<10; i++) {
    console.log(i);
}

console.log(i); //10

W kolejnych rozdziałach natrafisz na przykłady bardziej praktyczne...

Kolejna cecha rozróżniająca var od let/const jest taka, że zmienne var możemy ponownie deklarować, co jest niemożliwe w przypadku let i const:


var name = "Marcin";
var name = "Karol";
console.log(name); //Karol

let name = "Marcin";
let name = "Karol"; //błąd = Identifier "name" has already been declared
console.log(name);

W przypadku const/let musimy stosować inne bloki:


{
    let name = "Marcin";
    console.log(name);
}
{
    let name = "Karol";
    console.log(name);
}

Kolejna różnica między starszą deklaracją var a jej młodszymi braćmi to tak zwany hoisting.
JavaScript lubi pomagać programiście. Jednym z takich przypadków pomocy jest niewidoczne dla programisty wynoszenie deklaracji na początek kodu. I tak na początek kodu wynoszone są deklaracje takie jak var / let/ const / function / class. Różnica jest w sposobie takiego wynoszenia.

W przypadku var odwołanie się do zmiennej przed jej stworzeniem nie rzuci nam błędem, natomiast pokaże undefined:


var a; //niewidocznie przenoszona jest sama deklaracja - bez wartości
console.log(a); //undefined
var a = 200;

Deklaracja zmiennej bez wartości wynoszona jest automatycznie na początek danego kodu (a w zasadzie na początek danego zakresu - np. na początek danej funkcji), w wyniku czego nasz powyższy skrypt w momencie wykonywania ma postać:


var a; //js przeniósł tutaj deklarację zmiennej ale bez jej wartości!
console.log(a); //wypisze undefined, ale błędu nie ma

var a = 20;

Niektórzy programiści - szczególnie ci, którzy na co dzień używali innych języków, w których takie zjawisko nie występuje - dla uniknięcia takich niejawnych (a i niewidocznych dla nas) zachowań - stosowali konwencję deklaracji każdej zmiennej na początku kontekstu:


var text, age; //jeżeli nie podstawiamy wartości to równe są undefined

age = 20;
text = "Ala ma " + age + " lat";

W przypadku let/const (ale też class) hoisting także istnieje, ale nie jesteśmy w stanie używać zmiennych przed ich zadeklarowaniem:


console.log(a); //Error: Cannot access "a" before initialization

let a = 20;

To samo będzie się tyczyło wyrażeń funkcyjnych, czyli sytuacji, gdzie funkcję podstawiamy pod zmienną. Znowu - nie możemy użyć zmiennej przed jej stworzeniem:


myFn(); //Error: Cannot access "myFn" before initialization

const myFn = function() {
    console.log("Lubie koty i psy");
}

Ale o tym porozmawiamy sobie w rozdziale o funkcjach.

Miejsce przed deklaracją zmiennej let/const zwie się temporal dead zone, bo nie możemy odwoływać się do zmiennej, której jeszcze nie zadeklarowaliśmy. Dzięki takiemu zabiegowi nasz kod staje się bardziej logiczny - najpierw tworzymy wszystkie klocki (zmienne, funkcje), a dopiero potem ich używamy.

Ostatnią różnicą - dość mało znaną - jest to, że deklarując zmienną globalną var (poza ciałem funkcji), dodawana jest ona jako właściwość obiektu window. W przypadku let nic takiego się nie dzieje:


var a = 20;
let b = 30;

console.log(window.a); //20
console.log(window.b); //undefined

Gdy wypiszemy sobie w konsoli obiekt window, pokaże nam się masa różnych rzeczy, które możemy (i zapewne będziemy) używać:


    console.log(window);
Nieumyślne nadpisanie

Tworząc w powyższy sposób globalne zmienne var, moglibyśmy przez przypadek nadpisać niektóre funkcjonalności, które w przyszłości chcielibyśmy użyć:


var alert = "Tekst informacji";
var fetch = "Pobieram dane";
Nieumyślne nadpisanie

alert("Wypełnij te pola"); //błąd
fetch("https://jsonplaceholder.typicode.com/") //błąd

Przy czym nie powinienem tutaj demonizować. Jeżeli piszesz swoje skrypty cokolwiek ponad 0 poziom wtajemniczenia, to raczej nie powinieneś nigdy natrafić na takie błędy. Ale któż wie...

To jak jesteśmy przy głupotach JS (o czym można by niezły art napisać), to jedną z kolejnych rzeczy jest fakt, że dla każdego elementu z ID tworzona jest zmienna o takiej samej nazwie, która wskazuje na dany element:


//nie stworzyłem nigdzie takiej zmiennej ale mam do niej dostęp
//bo na tej stronie jest element #mainContent (główna treść)

console.log(mainContent);

W odczuciu autora tego kursu jest to złe, ponieważ dostajemy zmienne, które powstają z nieba. Jeżeli będziemy pracować na elementach z ID, mimo że JavaScript stworzył dla nich zmienne, mimo wszystko lepszym wydaje się je jawnie pobrać za pomocą odpowiednich funkcji. Ale to już w rozdziale o DOM

Podsumowanie

Ok podsumujmy powyższe rozważania.

  • Zmienne to rodzaje pudełek, w których możemy trzymać różne rzeczy.
  • Zmienne możemy tworzyć za pomocą słów kluczowych var/let/const, przy czym zalecane są te dwa ostatnie
  • Let/const różnią się od varów głównie zasięgiem oraz tym, że w jednym zasięgu (bloku) nie możemy ponownie tworzyć zmiennych o tej samej nazwie.
  • Hoisting to zjawisko wynoszenia na początek skryptu zmiennych i deklaracji funkcji
  • W naszych skryptach starajmy się używać jak najwięcej const - dzięki temu będziesz wyglądał jak pro. Jedynym wyjątkiem są liczniki oraz zmienne które wiemy, że zaraz zmienimy (np. toggleCatNightPartyMode)

I w zasadzie tyle. Cała reszta przyjdzie z praktyką. Nawet jeżeli na chwilę obecną powyższe rozważania wydają się dla ciebie dziwne, nie przejmuj się. Po 2-3 skryptach całość stanie się odruchem.

Trening czyni mistrza

Jeżeli chcesz sobie potrenować zdobytą wiedzę z tego działu, to zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania-podstawy

W repozytorium jest branch "solutions". Tam znajdziesz przykładowe rozwiązania.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.

Menu