Przewodnik po zmiennych w JavaScript - część 1

Zmienne to jedno z najbardziej podstawowych pojęć w programowaniu. Na wysokim poziomie są one nazwami, do których możesz przypisać wartości, a następnie uzyskać do nich dostęp i używać ich do wykonywania operacji. Ale to duże uogólnienie, bo faktycznie do zrozumienia jest o wiele więcej, a JavaScript ma pewne niuanse, które tego nie ułatwiają.

W JavaScript istnieje kilka sposobów deklarowania zmiennych. Zmienne mogą zachowywać się różnie w zależności od tego, jaka wartość jest do nich przypisana. Zmienne mają zakres, który określa, gdzie i kiedy można uzyskać do nich dostęp.

W pierwszej części tego artykułu opowiemy czym naprawdę są zmienne i jak najlepiej myśleć o zmiennych. W części drugiej zajmiemy się sposobami deklarowania zmiennych, oraz zakresem zmiennych w JavaScript.

Czytaj dalej, aby dowiedzieć się wszystkiego, co musisz wiedzieć o zmiennych.

Jak rozumieć zmienną?

Dla przedstawienia koncepcji zmiennych często używana jest metafora pudełka o określonej nazwie. Tworzymy pudełko, nadajemy mu jakąś nazwę, na przykład "foo", i wkładamy do niego wartość, na przykład słowo “kot”:

Rysunek pudełka zawierającego słowo “kot”

Następnie możemy sięgnąć po to pudełko, by odzyskać jego zawartość. Możemy też włożyć do niego coś innego. Ale, co ważne, w dowolnym momencie w pudełku może znajdować się tylko jedna wartość:

Rysunek pudełka z którego usunięto słowo “kot”, a włożono słowo “pies”, oraz przekreślonego pudełka w któym umieszczono dwa słowa

W kodzie JavaScript mogłoby to wyglądać tak:

// tworzymy pudełko i umieszczamy w nim początkową wartość
var foo = "kot";
// sięgamy po pudełko by wyjąć jego zawartość
console.log(foo);
// wkładamy coś innego
foo = 7;
// i znów odzyskujemy zawartość
console.log(foo);

Na tym poziomie wygląda to naprawdę jasno i prosto. Ale metafora pudełka załamie się, gdy spróbujemy przechować w zmiennej wartość, która nie jest typem prostym (typy proste i złożone to temat na osobny artykuł, na ten moment wystarczy, że wiesz, iż string, number i boolean są typami prostymi, a tablice i obiekty typami złożonymi.)

Spójrz na taki przykład:

// tworzymy zmienna i przypisujemy do niej tablicę z 3 elementami
var foo = [10, 20, 30];
// tworzymy nową zmienną i przypisujemy do niej wartość zmiennej foo
var bar = foo;
// zmieniamy wartość pierwszego elementu tablicy przypisanej do foo
foo[0] = 99;
// i teraz sprawdzamy wartość zmiennej bar
console.log(bar);

Jak myślisz, jaki rezultat da ostatni console.log?

Będzie to:

[99, 20, 30];

Jak widać, gdy zmieniliśmy wartość elementu tablicy foo, zmienił się on również w tablicy bar. Dlaczego tak się dzieje? Pozostając przy wyobrażeniu pudełka, czy ta sama tablica znajduje się jednocześnie w obydwu pudełkach? A może zmienna bar zawiera zmienną foo? Żadna z tych rzeczy nie jest prawdą, ale zanim wyjaśnię o co tak naprawdę tutaj chodzi, chciałbym zaproponować Ci inny sposób myślenia o zmiennych. Zamiast metafory pudełka, proponuję metaforę etykiety.

Działa ona tak. Na żółtej karteczce tworzymy etykietę foo. Najpierw przyklejamy ją do słowa “kot”, by następnie ją odczepić i przykleić do liczby 7.

Rysunek składa się z 2 klatek. W obydwu klatkach znajdują się 2 wartości: słowo “kot” i liczba 7. W pierwszej klatce etykieta foo jest przyczepiona do słowa “kot”, w drugiej klatce do liczby 7

Spróbujmy teraz zastosować tę metaforę do przykładu z tablicą:

  1. Tworzymy etykietę foo i przypinamy ją do tablicy z 3 elementami.
  2. Następnie tworzymy nową etykietę bar, i przypinamy ją do tego, do czego przypięta jest etykieta foo, czyli do tej samej tablicy.
  3. Gdy teraz zmienimy wartość któregoś z elementów tablicy, to ta zmieniona wartość będzie widoczna zarówno gdy sięgniemy do tablicy za pomocą etykiety foo, jak i poprzez etykietę bar.

3 klatki. W pierwszej klatce etykieta foo jest przyczepiona do tablicy 10, 20, 30. W drugiej klatce do tej samej tablicy dodana jest druga etykieta: bar. W trzeciej klatce zmieniony został pierwszy element tablicy na liczbę 77. Obie zmienne foo i bar odzwierciedlają tę zmianę w momencie wywołania console.log()

Tak jak pudełko nie mogło zawierać dwóch wartości jednocześnie, tak samo i etykieta nie może być przypięta jednocześnie do dwóch różnych rzeczy. Natomiast dwie lub więcej etykiet może być przypiętych do tej samej rzeczy (mam na na myśli dosłownie tę samą rzecz, a nie wyglądjącą tak samo kopię). W tym sensie, metafora etykiety tworzy o wiele lepszy model mentalny tego, jak naprawdę działają zmienne.

Czym naprawdę jest zmienna?

Komputer przechowuje dane w komórkach pamięci RAM. Komórki te mają numeryczne adresy, dzięki którym procesor jest w stanie do nich sięgnąć i odczytać lub zapisać informację.

Kilka komórek pamięci RAM o numerach 1024-1028. Każda komórka zawiera wartość liczbową

JavaScript jest językiem wysokiego poziomu, więc programując w nim nie musimy zajmować się bezpośrednio operacjami na pamięci RAM, wykonuje je dla nas silnik JavaScript.

Silnik JavaScript podczas wykonywania programu korzysta z dwóch obszarów pamięci: stosu (stack) oraz sterty (heap). Stos jest mniejszy i uporządkowany, JS przechowuje w nim środowiska aktualnie wykonywanych funkcji (execution contexts). Wartości proste (primitive values) przechowywane są bezpośrednio w stosie. Sterta jest znacznie większa, nieuporządkowana, i służy do przechowywania wszystkich tych danych, które nie są trzymane w stosie. To tutaj znajdują się dane typów złożonych: tablic i obiektów.

Każda dana, zarówno w stosie jak i w stercie, zajmuje pewną liczbę komórek pamięci, począwszy od określonego adresu. Zmienne pozwalają nam korzystać z tych danych. W jaki sposób? W tym miejscu pomoże nam metafora etykiety, bo w rzeczywistości zmienna jest nazwą wskazującą na miejsce w pamięci. Zmienna przechowuje nie samą wartość, ale adres pamięci, w którym dana wartość się znajduje.

Symbolicznie przedstawione stos i sterta w pamięci RAM. W stosie znajdują się 2 zmienne: foo i bar. Zmienna foo wskazuje na wartość “kot” w stosie. Zmienna bar wskazuje na tablicę położoną w stercie

Czyli przypisując do zmiennej nową wartość, faktycznie zmieniamy informację o wskazywanym przez nią adresie pamięci RAM. Przepinamy etykietę.

W momencie przypisania do zmiennej nowej wartości, dla typów prostych ta wartość zastępuje starą (stara zostaje zapomniana), a w przypadku typów złożonych zmieniany jest tylko sam wskaźnik (referencja) do adresu w stercie.

Dlatego możemy mieć dwie zmienne wskazujące na dosłownie tę samą tablicę. Użyta we wcześniejszym przykładzie 3-elementowa tablica jest przechowywana w stercie, a zmienne foo i bar zawierają adres pamięci RAM, w którym dane owej tablicy mają swój początek.

Ponadto, JavaScript jest językiem o dynamicznym typowaniu, więc ta sama zmienna może w jednym momencie wskazywać na wartość prostą przechowywaną w stosie, a chwilę później na wartość złożoną zlokalizowaną w stercie.

Działania na zmiennych

Napiszę jeszcze krótko o działaniach na zmiennych. Krótko, bo… w rzeczywistości nie działamy na zmiennych! Faktycznie za pośrednictwem zmiennych wykonujemy działania na wartościach, na które te zmienne wskazują.

Czyli:

var str1 = "Java",
str2 = "Script";
console.log(str1 + str2);

jest po prostu tym samym co:

console.log("Java" + "Script");
// rezultat: JavaScript

Dlatego nie będziemy teraz rozważać jakie mogą być rezultaty różnych działań (np. 2 + "3") i dlaczego tak się dzieje. Ten temat nie ma związku ze zmiennymi jako takimi i zasługuje na osobny artykuł.

Podsumowanie

Najważniejsze informacje do zapamiętania z tej części artykułu:

  • Zmienne są jak etykiety przypięte do danych przechowywanych w pamięci RAM.
  • Zmienne w JavaScript zachowują się inaczej gdy przypisujemy do nich wartości proste, a inaczej gdy wartości złożone.
  • Dla wartości prostych każda zmienna wskazuje na indywidualną kopię danej wartości przechowywaną w stosie.
  • Dla wartości złożonych zmienne zawierają referencje do obiektów położonych w stercie.

W drugiej części przewodnika po zmiennych zajmiemy się zasięgiem zmiennych oraz różnymi sposobami deklarowania zmiennych. Omówimy też łańcuch zasięgów oraz przesłanianie zmiennych:

Przewodnik po zmiennych w JavaScript - część 2

Ikona Smiley Obserwuj DevSchool!
Logo FacebookLogo Twitter

Copyright 2022 – 2023 ©
Michał Wilkosiński CONIFER MEDIA
Wszystkie prawa zastrzeżone