Ten artykuł jest częscią serii

Jeśli nie przeczytałeś jeszcze poprzednich artykułów, to zawsze możesz to nadrobić klikając w przycisk.

Miniatura artykułu

JavaScript - przewodnik po zmiennych

18 minut

Skopiuj link

Data publikacji: 10/8/2023, 5:28:45

Ostatnia aktualizacja: 10/8/2024

Wstęp

W poprzednim artykule z tej serii przybliżyliśmy ogólny zarys języka JavaScript, wspominając o kluczowym elemencie każdego języka programowania - zmiennej. Zaznaczyliśmy, że zmienna to nic innego jak "pudełko" do przechowywania wartości, które posiada swoją nazwę, wartość itd. Wydawać by się mogło, że ten temat jest prosty i nie ma potrzeby zagłębiania się w jego tajniki. Nic bardziej mylnego!

Zmienne w JavaScript są nie tylko podstawą, ale także niezmiernie złożonym i wielowymiarowym elementem tego języka. Sposób, w jaki są deklarowane, jakie typy danych można w nich przechowywać, czy to, jak zachowują się w różnych kontekstach, może być źródłem nieporozumień, a nawet błędów dla wielu programistów - zarówno początkujących, jak i tych bardziej zaawansowanych.

W tym artykule zagłębimy się w świat zmiennych w JavaScript, zrozumiemy ich naturę, różnice między typami zmiennych, a także dowiemy się, jak efektywnie nimi zarządzać. Dlaczego let nie jest tym samym co var? Czym jest zakres zmiennej? Jak różne typy zmiennych zachowują się w pamięci? Na te i wiele innych pytań postaramy się odpowiedzieć w kolejnych sekcjach.

Deklaracja zmiennych

Sposób, w jaki deklarujemy i przypisujemy wartości do zmiennych, ma głęboki wpływ na to, jak nasz kod działa i jakie ma właściwości. W JS, w zależności od użytego słowa kluczowego, zmienne mogą zachowywać się inaczej. Zaczniemy od najstarszego i kiedyś najbardziej popularnego sposobu deklaracji - słowa kluczowego var.

Słowo kluczowe var

W pierwszych wersjach tego języka, var było głównym słowem kluczowym używanym do deklaracji zmiennych. Zmienne zadeklarowane za pomocą var mają pewne specyficzne właściwości:

  • zasięg funkcji (function scope) - oznacza to, że zmienna zadeklarowana wewnątrz funkcji jest dostępna tylko w tej funkcji, a jeśli jest zadeklarowana poza funkcją, staje się zmienną globalną.

  • brak zakresu blokowego - w przeciwieństwie do innych języków programowania, var nie respektuje zakresu blokowego. Oznacza to, że zmienna zadeklarowana wewnątrz pętli lub instrukcji warunkowej jest dostępna poza tym blokiem.

  • re-deklaracja zmiennej - w przypadku var możesz wielokrotnie deklarować zmienną o tej samej nazwie w tym samym zakresie, a poprzednia deklaracja zostanie zastąpiona przez nową. Nie dostaniemy błędu, ale taki kod jest trudniejszy do debugowania i może prowadzić do nieprzewidywalnych błędów w większych projektach.

Dla dokładniejszego zrozumienia tej charakterystyki - jeśli zadeklarujesz zmienną (w przypadku słowa kluczowego var) wewnątrz funkcji (nawet w instrukcji warunkowej) to będzie ona miała zakres tylko w tej funkcji, jeśli zrobisz to poza funkcją np. w bloku instrukcji warunkowej if to ta zmienna będzie miała zakres globalny, co oznacza, że będzie dostępna wszędzie. Na samym końcu w przykładzie widać też, jak zgubna może być re-deklaracja.

Słowo kluczowe let

Słowo kluczowe let zostało wprowadzone w ES6 (ECMAScript 2015) jako nowoczesna alternatywa dla słowa kluczowego var. Dzięki let możliwe jest deklarowanie zmiennych, które mają być zmieniane w trakcie działania programu, ale z pewnymi ulepszeniami w zakresie zarządzania zasięgiem.

Oto najważniejsze właściwości zmiennych deklarowanych za pomocą let:

  • zakres blokowy (block scope) - zmienne deklarowane za pomocą let mają zakres blokowy. Ich zasięg ogranicza się do najbliższego otaczającego bloku, w którym zostały zadeklarowane, niezależnie od tego, czy jest to funkcja, instrukcja warunkowa, pętla czy nawet zwykły blok kodu w klamrach {}.

  • brak re-deklaracji - w jednym zakresie nie możesz zadeklarować zmiennej o tej samej nazwie więcej niż raz za pomocą let. W przypadku var takie ponowne deklaracje są dozwolone i nie powodują błędów.

  • zmienna o zmiennym stanie - zmienna deklarowana za pomocą let pozwala na modyfikację jej przypisanej wartości, co odróżnia ją od zmiennej deklarowanej za pomocą const. W przypadku const, nie możemy przypisać do niej nowej wartości po inicjalizacji, ale jeśli przypisany obiekt ma właściwości, możemy modyfikować te właściwości.

W porównaniu z var, let oferuje programistom bardziej przewidywalne zachowanie i unika wielu pułapek związanych z użyciem var, mimo że składnia deklaracji jest bardzo podobna.

Słowo kluczowe const

Słowo kluczowe const w JavaScript pozwala na deklarowanie zmiennych, które nie mogą zostać ponownie przypisane. Oznacza to, że jednorazowo zadeklarowana zmienna z użyciem const nie może być zmieniona na inną wartość. Chociaż może się to wydawać podobne do stałych w innych językach programowania, const w JavaScript ma kilka specyficznych cech.

  • zakres blokowy (block scope) - podobnie jak let, const ma zakres blokowy. Oznacza to, że zmienna jest dostępna tylko wewnątrz bloku, w którym została zadeklarowana.

  • brak możliwości ponownego przypisania - różnica między let a const polega na tym, że zmienne zadeklarowane za pomocą const nie mogą zostać ponownie przypisane.

Zastosowanie const w kontekście typów prostych i złożonych

Jak wcześniej wspomniałem, const pozwala na deklarację zmiennej, której wartość nie może być później ponownie przypisana. Nie możemy przypisać nowej wartości do zmiennej const, jeśli odnosi się ona do obiektu, ale możemy modyfikować jego właściwości.

  • typy proste - kiedy deklarujemy zmienną jako const i przypisujemy do niej wartość typu prostego (np. liczba, łańcuch znaków, wartość logiczna), nie możemy tej wartości później zmienić. Każda próba przypisania nowej wartości skutkuje błędem.

  • typy złożone - dla zmiennych o typach złożonych (np. obiekty, tablice), const zapobiega tylko ponownemu przypisaniu całego obiektu lub tablicy. Jednak wciąż możemy modyfikować ich zawartość, czyli np. właściwości tam znajdujące.

Podsumowując, const nie czyni obiektów czy tablic niemutowalnymi w kontekście ich wewnętrznych wartości. Gwarantuje jedynie, że inna wartość lub obiekt nie zostanie przypisana do zmiennej. Jeśli chcemy mieć pewność, że wewnętrzne wartości obiektu nie zostaną zmienione, musimy posłużyć się innymi technikami, takimi jak Object.freeze(). Choć nie jest to idealne rozwiązanie, bo pozwoli nas uchronić jedynie przed mutacjami na tzw. płytkim poziomie, oznacza to że gdy nasz obiekt będzie miał w sobie jako właściwości - inne obiekty, to ich wartości będą już mutowalne. Jest to już bardziej zaawansowany temat wykraczający poza dzisiejszy temat.

Hoisting

Hoisting to zachowanie JavaScript, które polega na "wynoszeniu" deklaracji zmiennych i funkcji na początek ich zakresu. Innymi słowami, niezależnie od tego, gdzie w kodzie deklarujesz zmienną lub funkcję, JavaScript traktuje to tak, jakbyś zrobił to na jego samym początku. Jednak same wartości przypisane do tych zmiennych nie są "wynoszone", tylko ich deklaracje.

Jak to działa dla var?

Gdy deklarujesz zmienną za pomocą var, jej deklaracja (ale nie wartość) jest "wynoszona" na początek zakresu. Dlatego też, kiedy próbujesz odnieść się do zmiennej przed jej zadeklarowaniem, dostaniesz wartość undefined zamiast błędu.

Choć wydaje się, że odwołujesz się do variable przed jej zadeklarowaniem, JavaScript "przenosi" deklarację na górę, więc rzeczywiście kod jest traktowany tak:

Jak to działa dla const, let?

Dla let i const, hoisting także występuje, ale z jednym ważnym wyjątkiem: nie możesz odnieść się do tych zmiennych przed ich zadeklarowaniem.

Gdy JS napotka deklaracje za pomocą let i const, zakłada "czasową martwą strefę" (Temporal Dead Zone) od początku zakresu aż do linii, w której deklaracja faktycznie występuje. Odwołanie się do zmiennej w tej strefie, powoduje błąd.

Podsumowując dla var, hoisting wynosi deklarację zmiennej na górę zakresu i automatycznie przypisuje jej wartość undefined. Natomiast dla let i const, deklaracja również jest wynoszona, ale zmienna nie jest automatycznie inicjalizowana wartością undefined. Zamiast tego, do momentu faktycznej deklaracji zmiennej w kodzie, każde jej wywołanie skutkuje błędem.

Zakres leksykalny

Zakres leksykalny (lexical scope) to jedno z ważnych pojęć w programowaniu, które odnosi się do sposobu, w jaki zmienne i funkcje są dostępne w kodzie. Wcześniej opisywałem działanie zakresu globalnego i blokowego, gdy jest to dla Ciebie zrozumiałe nie będziesz mieć problemu ze zrozumieniem czym jest zakres leksykalny. Zakres leksykalny opiera się na miejscu, w którym zmienna lub funkcja jest zadeklarowana, a nie na miejscu, w którym jest wywoływana. W związku z tym funkcje w JavaScript mają dostęp do zmiennych zadeklarowanych w ich otaczającym zakresie, nawet jeśli są one wywoływane w innych miejscach. Funkcje w JavaScript mogą być zagnieżdżane wewnątrz innych funkcji, tworząc hierarchię zakresów. Funkcja wewnętrzna ma dostęp do zmiennych i funkcji swojego otaczającego zakresu oraz do wszystkich zakresów, które otaczają jej otaczający zakres, aż do globalnego zakresu. Zakres leksykalny jest ściśle powiązany z kolejnym omawianym mechanizmem - closure.

Closure

Domknięcia (closures) często sprawiają problem, szczególnie początkującym, lecz postaram się to przedstawić najlepiej jak potrafię. Domknięcie jest to termin techniczny używany do opisania funkcji, która "zapamiętuje" otaczający ją zakres, nawet jeśli jest używana w innym miejscu w kodzie, gdzie ten zakres nie jest bezpośrednio dostępny. Gdy mamy funkcję wewnątrz innej funkcji, to wewnętrzna funkcja ma dostęp do zmiennych zewnętrznej funkcji, nawet po zakończeniu działania tej zewnętrznej funkcji. To jest kluczowa cecha domknięć. Przykład powinien nieco rozjaśnić sytuację:

A teraz krok po kroku przejdźmy przez to co dzieje się w przykładzie powyżej:

  1. tworzymy funkcję createCounter

  2. w jej wnętrzu deklarujemy zmienną num przypisując jej wartość 0

  3. dodatkowo zwracamy w niej inną, anonimową funkcję (nienazwaną). Ta anonimowa funkcja, kiedy jest wywoływana, zwraca zinkrementowaną wartość zmiennej num.

  4. kiedy wywołujesz createCounter(), zwracana jest wewnętrzna funkcja. Ta anonimowa funkcja jest przypisywana do zmiennej o nazwie counter. Ważne jest, aby zrozumieć, że mimo zakończenia wywołania createCounter(), zwrócona funkcja wciąż "pamięta" swoje otaczające środowisko - w tym przypadku zmienną num. Dlatego mówimy, że zwrócona funkcja stanowi domknięcie (closure).

  5. gdy wywołujesz counter() po raz pierwszy (poprzez console.log(counter()); zwracana jest aktualna wartość num (czyli 0, a następnie num jest inkrementowane. Gdy wywołujesz counter() po raz drugi, zwracana jest aktualna wartość num (teraz 1, ponieważ została wcześniej zinkrementowana), a następnie num jest znowu inkrementowane.

Dlaczego to działa? Dzięki mechanizmowi domknięć w JavaScript. Zwrócona funkcja "zapamiętuje" swoje otaczające środowisko, co pozwala jej na dostęp do zmiennej num nawet po zakończeniu działania funkcji createCounter.

Typy danych

JavaScript jest językiem dynamicznie typowanym, co oznacza, że nie musisz deklarować typu zmiennej podczas jej inicjalizacji. Zamiast tego, JS automatycznie przydzieli odpowiedni typ w zależności od przypisanej wartości. Dzielimy je na typy proste oraz złożone:

Typy proste

Aktualnie w JavaScript dysponujemy takimi typami prostymi:

string

reprezentuje ciągi znaków. Tekst w JS jest niezmienny, co oznacza, że ​​po utworzeniu ciągu znaków nie można go modyfikować, ale można tworzyć nowe ciągi na podstawie istniejących.

  • niezmienność - ciągi znaków są niezmienne, co oznacza, że raz stworzony ciąg nie zmieni się "sam z siebie". Jeśli chcemy coś w nim zmienić, zawsze otrzymujemy nowy ciąg.

  • tworzenie ciągów znaków - możemy używać pojedynczych cudzysłowów ('), podwójnych cudzysłowów (") lub grawisów (`) do definiowania ciągów. Backticks pozwalają na tzw. "template strings", które ułatwiają wstawianie wartości zmiennych do tekstu.

  • operacje na ciągach - ciągi znaków mają wiele przydatnych właściwości i metod, takich jak length (długość ciągu), toUpperCase() (konwersja na wielkie litery), split() (dzielenie ciągu) i wiele innych.

  • kodowanie znaków - w JavaScript używa UTF-16 do reprezentowania ciągów, co pozwala nam na używanie wielu różnych znaków i symboli.

  • znaki specjalne - W ciągach możemy używać specjalnych sekwencji, jak \n dla nowej linii czy \t dla tabulacji.

  • łączenie ciągów - możemy połączyć dwa ciągi za pomocą + (konkatenacja) lub użyć interpolacji w template strings, czyli wstawić wartość zmiennej bezpośrednio do tekstu.

number

typ number w JavaScript jest używany do reprezentowania zarówno liczb całkowitych, jak i zmiennoprzecinkowych. Oto kilka kluczowych informacji i cech tego typu:

  • całkowite i zmiennoprzecinkowe - w JavaScript nie ma osobnych typów dla tych liczb. Wszystkie liczby są traktowane jako liczby zmiennoprzecinkowe podwójnej precyzji.

  • wartości specjalne - typ number posiada kilka specjalnych wartości, takich jak: Infinity, -Infinity i NaN (Not a Number). Na przykład, dzielenie przez zero w JS nie powoduje błędu, ale zwraca Infinity.

  • operacje matematyczne - JS oferuje wbudowany obiekt Math, który zawiera wiele funkcji i stałych do przeprowadzania operacji matematycznych.

  • literały liczbowe - można deklarować liczby w różnych systemach liczbowych, takich jak dziesiętny, szesnastkowy, ósemkowy czy binarny, używając odpowiednich prefiksów (np. 0x dla szesnastkowego).

  • precyzja zmiennoprzecinkowa - chociaż większość operacji jest precyzyjna, ich sposób przechowywania w systemie bitowym, który nie potrafi wiernie odwzorować niektórych wartości może prowadzić do niewielkich błędów zaokrąglenia. JavaScript może czasami dawać niespodziewane wyniki, np. 0.1 + 0.2 !== 0.3.

boolean

przy użyciu typu boolean przedstawiamy wartości prawdy (true) lub fałszu (false). Jest to podstawowy typ danych, który odgrywa swoją rolę w wielu aspektach programowania, zwłaszcza w kontrolowaniu przepływu programu poprzez instrukcje warunkowe.

  • konwersja na typ boolean - inne typy danych mogą być automatycznie konwertowane na wartości boolean, jeśli jest taka potrzeba. Na przykład w instrukcjach warunkowych. Wiele wartości w JavaScript jest traktowanych jako "prawdziwe" - truthy (np. niepuste ciągi, liczby różne od zera), podczas gdy inne są "fałszywe" - falsy (np. 0, null, undefined, "").

  • operatory porównania - często korzystamy z wartości boolean przy użyciu operatorów porównania, takich jak ==, ===, >, <, >=, <=. Te operatory zwracają wartość typu boolean w zależności od wyniku porównania.

  • operatory logiczne - możesz manipulować wartościami boolean za pomocą operatorów logicznych, takich jak && (AND), || (OR) oraz ! (NOT). Pozwalają one na tworzenie bardziej złożonych warunków.

undefined

typ, który ma jedną wartość - undefined. Oznacza, że zmienna została zadeklarowana, ale nie ma jej jeszcze przypisanej żadnej wartości.

  • domyślna wartość - jeśli zadeklarujesz zmienną, ale nie przypiszesz jej żadnej wartości, to jej domyślną wartościa będzie undefined.

  • różnica między niezdefiniowaną, a niezadeklarowaną zmienną - próbując uzyskać dostęp do zmiennej, która nie została zadeklarowana, otrzymasz błąd.

  • jawne przypisanie wartości undefined - możesz również jawnie przypisać wartość undefined do zmiennej, chociaż zazwyczaj nie jest to zalecane, ponieważ może to wprowadzić niejasności w kodzie.

  • sprawdzanie, czy zmienna jest undefined - możesz sprawdzić, czy zmienna ma wartość undefined, używając operatora typeof. Oczywiście z jego pomocą możesz też sprawdzać inne typy.

null

typ null w JavaScript reprezentuje brak wartości lub celowe "nic". Jest to specjalna wartość, która oznacza "brak wartości" lub "pusty obiekt". Chociaż null jest często porównywane z undefined, mają one różne zastosowania i znaczenia.

  • celowe przypisanie - w przeciwieństwie do undefined, który często pojawia się w sposób niezamierzony, null jest zazwyczaj używane w celu jawnego wskazania, że zmienna powinna w danym momencie być "pusta".

  • porównanie z undefined - wartość null i undefined są sobie równe w przypadku "płytkiego" porównania (==), dzieje się tak dlatego, że następuje tutaj konwersja typów, a że obie są falsy to warunek staje się prawdziwy. Gdybyś chciał porównać biorąc pod uwagę konkretne typy, a nie tylko ich konwersję lub sprawdzić czy zmienna ma wartość jakiegoś konkretnego typu musisz zastosować porównanie "głębokie" (===)

  • ciekawostka z null - gdy użyjesz operatora typeof na wartości null, dostaniesz wynik "object". Jest to znany błąd w języku JavaScript, ale nie został on poprawiony ze względu na zachowanie wstecznej kompatybilności.

symbol

typ ten został wprowadzony w ES6 (ECMAScript 2015), symbol to unikalna i niemodyfikowalna wartość początkowa, głównie używana jako identyfikator dla właściwości obiektów. Każdy symbol jest gwarantowanie unikalny. Jest to typ, który wymaga osobnego artykułu dla pełnego zrozumienia, który znajdziecie na naszym blogu.

bigInt

to stosunkowo nowy typ danych w JavaScript, który został wprowadzony w ES2020, aby rozwiązać pewne ograniczenia związane z liczbami w JS. Pozwala on na bezpieczne przechowywanie i operowanie na bardzo dużych liczbach całkowitych, które przekraczają bezpieczny zakres dla standardowego typu number.

  • kiedy używać bigInt? - JavaScript używa 64-bitowego formatu do reprezentacji liczb zmiennoprzecinkowych, co sprawia, że istnieje tzw. "bezpieczny zakres" dla liczb całkowitych. Jest on ograniczony do wartości od -2^53 do 2^53. Jeżeli musisz operować na liczbach poza tym zakresem, bigInt jest odpowiedzią na twoje potrzeby.

  • tworzenie wartości bigInt - możemy dodać literę n na końcu liczby całkowitej lub użyć BigInt()

  • ograniczenia - na ten moment bigInt nie jest jeszcze wspierany we wszystkich środowiskach, więc warto zawsze to sprawdzić. Nie możesz także w operacjach matematycznych używać bigInt z typem number, dodatkowo nie posiada on też metody toFixed(), która jest dostępna na typie number.

Typy złożone

W przeciwieństwie do typów prostych, które są jednostkowymi wartościami, typy złożone reprezentują struktury, które mogą zawierać wiele wartości jednocześnie. Dzięki nim można tworzyć bardziej złożone i rozbudowane struktury danych w programie. Główne typy złożone w JavaScript dzielą się na:

Obiekty

  • obiekt - to zbiór par klucz-wartość, klucze znajdujące się w obiekcie są dla danego obiektu unikalne i wskazują na konkretne wartości. Tworzenie, modyfikacja i dostęp do tych wartości zostały opisane w poprzednim artykule z tej serii, więc aby nie powielać treści zachęcam do jego lektury

  • tablica - przechowuje uporządkowany zestaw elementów. Tak samo jak w przypadku obiektów opisałem to wcześniej.

  • funkcja - mogą być przypisywane do zmiennych, przekazywane jako argumenty oraz zwracane jako wartości. Służą do grupowania kodu, który wykonuje określone zadanie. Podstawowe użycie i zastosowanie poznaliśmy w poprzednim artykule. To co postanowiłem dodać to sposoby deklaracji. Funkcje możemy deklarować na kilka sposobów, które wymieniam bezpośrednio w kodzie:

Krótko opiszę każdy ze sposobów deklaracji:

  1. deklaracja funkcji (Function Declaration) - zdefiniowana poprzez słowo kluczowe function. Można ją wywołać przed jej deklaracją, dzięki mechanizmowi "hoisting", który wcześniej opisałem.

  2. wyrażenie funkcyjne (Function Expression) - funkcja jest przypisana do zmiennej. Nie jest wynoszona, więc nie można jej wywołać przed jej definicją.

  3. natychmiastowo wywoływane wyrażenie funkcyjne (IIFE) - funkcja, która jest natychmiast wywoływana zaraz po jej zdefiniowaniu. Używana do izolacji zakresu zmiennych i unikania zanieczyszczania globalnego zakresu.

  4. funkcja strzałkowa (Arrow Function) - krótsza składnia definiowania funkcji. Nie posiada wiązania this - jeśli ten temat nie jest Ci jeszcze znany, nie martw się, postaraj się zwyczajnie zapamiętać ten fakt.

  5. skrócona wersja funkcji strzałkowej - wersja funkcji strzałkowej, która skraca zapis, jeśli funkcja zawiera tylko jedną instrukcję (często używane do prostych operacji, takich jak mapowanie lub filtrowanie tablic).

  6. konstruktor funkcji - umożliwia tworzenie funkcji przez interpretację ciągu tekstowego. Ze względów bezpieczeństwa (np. ryzyko ataku poprzez wstrzykiwanie kodu) jest rzadko używany i generalnie niezalecany.

  • Date, Regexp, Error i inne wbudowane obiekty - tu chciałbym jedynie nadmienić, że JavaScript posiada wiele przydatnych wbudowanych obiektów, które służą do pracy z poszczególnymi sektorami. Z pomocą obiektu Date możesz manipulować datami i czasem, RegExp, czyli inaczej wyrażenia regularne będą pomocne przy wyszukiwaniu i zarządzaniu tekstem według określonych wzorców. Obiekt Error wesprze Cię podczas prezentowania błędów (o obsłudze błędów możesz poczytać tutaj). Oczywiście istnieją jeszcze inne bardzo przydatne obiekty, ale wylistowanie ich tutaj wszystkich nawet z krótkim opisem nie ma najmniejszego sensu, w miarę poznawania JS będziesz na bieżąco zgłębiać tajniki tego języka.

Mapy i zbiory

Mapy i zbiory to dwie przydatne struktury danych dostępne w JavaScript, które rozszerzają tradycyjne możliwości obiektów i tablic.

  • mapy (maps) - mapa przechowuje wartości w parach klucz-wartość, gdzie kluczem może być dowolny typ danych (w tym obiekty). W przeciwieństwie do obiektów, mapy zachowują kolejność wstawiania elementów. Podałem w kodzie poniżej kilka metod do zastosowania na mapach

  • zbiory (sets) - zbiór przechowuje tylko unikatowe wartości. Jeśli spróbujesz dodać tę samą wartość do zbioru, nie zostanie ona dodana ponownie. Tak samo, jak dla map, pokazałem podstawowe użycie w kodzie

Temat map i zbiorów zdecydowanie nie jest wyczerpany, ale nie wszystko naraz. Moim celem na tym etapie jest przedstawienie Ci możliwości oraz ogólnego zarysu możliwości. Istnieją także pewne odpowiedniki jak WeakMap oraz WeakSet, natomiast jest to już temat na kiedy indziej, gdzie też warto odpowiedzieć na pytania kiedy tak naprawdę podjąć decyzję o korzystaniu z tych struktur. Dziś skoncentruj się na zrozumieniu fundamentów, poznawanie kolejnych zagadnień stanowi postęp.

Przypisanie przez wartość, a może referencję?

Gdy mówimy o przypisaniu w programowaniu, można to porównać do umieszczania przedmiotów w pudełkach. Jak coś umieszczamy w jednym pudełku, może to wpłynąć (lub nie) na to, co jest w innym pudełku.

Przypisanie przez wartość

Wyobraź sobie, że masz zdjęcie, wydrukowałeś i dałeś je komuś. Ta osoba może je przyciąć, umieścić w ramce czy zrobić z nim cokolwiek innego, ale Twoje oryginalne zdjęcie pozostaje nienaruszone. W JavaScript, typy proste (jak liczby czy łańcuchy znaków) są przypisywane przez wartość.

Przypisanie przez referencję

Zamiast drukować wcześniej wspomniane zdjęcie, wysyłasz komuś link do zdjęcia zamieszczonego w Internecie. Jeśli ktoś zmieni to zdjęcie pod tym adresem (na przykład doda filtr), zarówno Ty, jak i osoba, której wysłałeś link, zobaczycie te zmiany, ponieważ obie osoby korzystają z tego samego źródła. W JavaScript, obiekty, tablice i inne typy złożone są przypisywane przez referencję.

Myślę, że przytoczona przeze mnie analogia jasno określa Ci, jakie są różnice pomiędzy przypisaniem przez wartość, a przypisaniem przez referencję.

A co z pamięcią?

Gdy deklarujesz zmienną w JavaScript, rezerwujesz miejsce w pamięci na przechowywanie jej wartości. Na przykład, kiedy piszesz let liczba = 5;, system przydziela miejsce w pamięci dla zmiennej liczba i przypisuje jej wartość 5. W językach programowania, w tym w JavaScript, pamięć komputera jest zwykle podzielona na stos (stack) i stertę (heap). Stos jest używany do przechowywania małych, prostych typów danych i informacji o kontekście wywołań funkcji, podczas gdy sterta jest używana do przechowywania większych struktur danych, takich jak obiekty (jest to kolejny z bardziej zaawansowanych tematów, ale warto wiedzieć, że tak się dzieje). JS ma wbudowany mechanizm zwany garbage collector, który automatycznie zwalnia nieużywaną pamięć. Kiedy zmienna lub obiekt nie jest już dostępny ani nie jest używany (np. po wyjściu z funkcji i braku innych referencji do niego), system oznacza go jako coś bezużytecznego i w pewnym momencie zwalnia zajmowane przez niego miejsce w pamięci. Życie zmiennej jest związane z jej zakresem. Zmienna zadeklarowana w funkcji istnieje tylko w trakcie wywołania tej funkcji. Po zakończeniu jej wywołania, miejsce w pamięci, które zajmowała taka zmienna, jest zwalniane (chyba że zachodzi domknięcie, które zostało opisane powyżej). Zmienne globalne natomiast istnieją przez cały czas działania programu. Podsumowując, zarządzanie pamięcią w JavaScript jest w dużej mierze automatyczne, ale zrozumienie, jak to działa, może pomóc w unikaniu pewnych pułapek i optymalizacji kodu.

Podsumowanie

Mam nadzieję, że ten artykuł rozjaśnił niejasne koncepcje związane ze zmiennymi w JavaScript. Pamiętaj, że zrozumienie tych podstawowych zagadnień jest kluczem do pisania skutecznego i optymalnego kodu. Zachęcam do dalszego eksplorowania i eksperymentowania już w praktyce.

Avatar: Maciej Mikołajczak

Front-end Developer

Maciej Mikołajczak

mcj.mikolajczak@gmail.com

Podziel się na

Dodaj komentarz

Komentarze (0)

Brak komentarzy

Jeżeli zainteresował Cię ten artykuł koniecznie przeczytaj inne artykuły z tej serii