Poznaliśmy wspólnie już całkiem dużo elementów języka JavaScript. Były zmienne, pętle, funkcje, typy danych i wiele innych. Jest jeszcze jeden, który stanowi FUNDAMENT dla programów mających robić coś naprawdę efektownego 🧩. Tym brakującym ogniwem jest tablica 🔥! Tablica w języku JavaScript wymagała dokładnego wytłumaczenia dużej ilości zagadnień, stąd też artykuł zrobił się przez to dużo większy, więc w razie czego znajdź sobie trochę więcej czasu na przestudiowanie wszystkiego ⏰.
TABLICA W JĘZYKU JAVASCRIPT TAKŻE POSTANAWIA IŚĆ WŁASNYM TOREM
Ten zawodnik też częściowo łamie ogólnie przyjęte normy wynikające z pierwszego języka wysokiego poziomu w historii, C (opinie są różne co do jego "wysokopoziomowości", natomiast dla mnie jest językiem wysokiego poziomu 🙂) ⚠️. Wyjaśnijmy sobie wpierw cechy charakterystyczne.
TABLICA "TAK OGÓLNIE"
Tablica (ang. array) pełni funkcję przechowalni wielu wartości w jednym miejscu tego samego typu, chociaż w języku JavaScript nie jest to do końca prawdą (wrócimy do tego za moment) 🤯. Przypomina taki "słoik", do którego wrzucamy elementy (będące wartościami nazywanymi "argumentami"), a one są odpowiednio ponumerowane 1️⃣2️⃣3️⃣4️⃣.
Najbardziej podstawową funkcją użycia tablicy jest grupowanie danych. Zamiast pisania 400 zmiennych nieznacznie różniących się etykietą (nazwą), możemy umieścić je wszystkie do jednej zmiennej tablicowej o wielkości 400 elementów i tam niech sobie siedzą ✅. To z kolei umożliwia nam zastosowanie pętli "for" do obsługi tych danych zamieniając pojedyncze instrukcje (jakie to byłoby okropne pisać 400 razy to samo 😱!) 🌟.
Gdybyś był(a) zainteresowany(-a) szczegółami tablicy od strony architektury, zobacz sobie artykuł ukazujący tę strukturę danych od strony teoretycznej ℹ️.
INICJALIZACJA TABLICY
Tablica w języku JavaScript inicjowana jest za pomocą pary nawiasów kwadratowych ([]). Wewnątrz nich umieszczamy opcjonalnie elementy, które mają się tam znajdować od samego początku. Wtedy każdy z nich oddzielamy przecinkiem.
Na przykład 👇:
const arrayA = []; // pusta tablica
const arrayB = [5, 67, 15, 8]; // tablica wypełniona liczbamiPierwsza tablica jest pusta (nie posiada w sobie żadnych elementów). Druga tablica posiada w sobie kilka liczb całkowitych.
PODOBNO NAZYWAJĄ CIĘ "OBIEKTEM"
Kolejna informacja ℹ️! Tablica w języku JavaScript to w rzeczywistości obiektowy typ danych ("object") 😱! Nie wierzysz? Sprawdź sobie tablicę przy użyciu wywołania metody "console.log" i użycia słowa "typeof":
const array = [];
console.log(typeof array);Co widzisz 😉?
ODWOŁYWANIE SIĘ DO ARGUMENTÓW
Powtórzę istotną rzecz: tablica jest zdolna do przechowywania wielu argumentów 🧺. Bardzo często występuje potrzeba dostania się do któregokolwiek z nich celem wykonania jakichś operacji (najczęściej w pętli, lecz nie zawsze ℹ️). Jak to się robi?
Celem dostania się do konkretnego argumentu, korzystamy z liczby naturalnej, która oznacza numer występowania w kolejności 🔢. Co więcej, liczony jest od zera !!! To znaczy, że pierwszy argument będzie znajdował się pod numerem 0, drugi pod numerem 1 itd. ⚠️
Oto przykład uzyskiwania dostępu do pierwszej wartości z tablicy 👇:
const array = [5, 6, 9];
console.log("Pierwsza liczba: " + array[0]);W wielu innych źródłach znajdziesz termin "indeks", który jest ładniejszym sformułowaniem tego numeru w kolejności ✨.
Liczenie od zera pochodzi z języka C i odwoływania się do adresu tablicy w systemie szesnastkowym. Tam indeks jest przesunięciem adresu o N bajtów, w zależności od typu danych tablicy ℹ️. Gdy indeks jest zerowy, to adres wskazuje na początek tablicy, który jednocześnie odpowiada za pierwszy argument. Wraz ze zwiększaniem się indeksu, adresu w pamięci "przesuwa się" coraz bardziej, co będzie oznaczać wskazywanie na kolejne argumenty (albo wyjście poza tablicę ⛔!). Tyle w dużym streszczeniu 😊.
OBSŁUGA WSZYSTKICH ELEMENTÓW W PĘTLI
Jak zdążyłem napisać, tablica świetnie komponuje się z pętlą "for". Kiedy potrzebujesz zaprogramować obsługę wszystkich elementów w tablicy, bardzo przydatne okaże się użycie właściwości "length", która zwraca aktualną liczbę argumentów w tablicy 👇:
const array = [5, 6, 9];
console.log(array.length); // 3Wtedy możesz bez problemu przedostać się bezpiecznie do każdej wartości, niezależnie od liczby posiadanych przez nią elementów:
for (let i = 0; i < array.length; ++i) {
console.log(array[i]);
}Zaczynając odliczanie od zera, a kończąc na właściwości "length", pętla przechodzi przez każdy pojedynczy element w tablicy, aż do wyczerpania ✅.
METODY TABLICY
Teraz zaprezentuję parę metod należących do tablicy, które oferują wykonywanie podstawowych operacji tzn. dodawanie i usuwanie z niej elementów (argumentów) oraz wykonywanie poszczególnych operacji na każdym z nich. Poznajcie się 🙂!
PUSH
"push" to wprowadzenie dodatkowego elementu do tablicy na jej sam koniec. Stąd też wymaga podania parametru w postaci elementu, jaki ma się tam znaleźć ℹ️. Przydaje się w momencie, gdy element z jakichś powodów, nie może zostać dodany do tablicy w chwili inicjalizacji (a tak często się zdarza).
To jest przykładowe użycie 👇:
const array = [5, 6, 9];
console.log(array);
array.push(38);
console.log(array);Metoda niczego nie zwraca, więc tylko wykonuje polecenie i tyle.
POP
Metoda "pop" zwraca element z końca tablicy (albo "undefined", jeśli nie ma czego zwrócić ⚠️) jednocześnie usuwając go z tablicy.
Oto przykład 👇:
const array = [5, 6, 9];
console.log(array);
const lastElement = array.pop();
console.log(array);
console.log(lastElement);Nie podajemy żadnych parametrów.
FOREACH
Kiedy trzeba wykonać jakąś operację na dowolnym zbiorze danych, to pętla "for" jest pierwszą rzeczą, która przychodzi do głowy 🧠. Jest jeszcze jeden zapis, który to umożliwia.
Każda tablica w języku JavaScript posiada metodę "forEach", która także iteruje po każdym z elementów tablicy, natomiast tutaj nie korzysta ze zmiennej "i" 😲. Za parametr przyjmuje - i tu uwaga - serię instrukcji do wykonania, czyli po prostu funkcję! I ta funkcja musi przyjmować jeden parametr typu "aktualnie badany element" 💡.
"forEach" oczekuje funkcji, która przyjmuje od jednego do trzech parametrów:
- pojedynczy element np. liczba,
- indeks (numer porządkowy aktualnego elementu),
- cała struktura danych, od której wywołuje się "forEach".
Pierwszy parametr musi się znaleźć obowiązkowo, a kolejne dwa są opcjonalne. Ich użyteczność zależy od postawionego problemu. Na przykład do wypisania elementów wystarczy nam tylko pierwszy, natomiast indeks może być potrzebny kiedy chcemy wypisać co drugi element (i % 2 == 0) i wpleść taki warunek w środek funkcji ✔️.
Rozpatrzmy kilka przykładów użycia.
FUNKCJA Z JEDNYM PARAMETREM OSADZONA W MIEJSCE PARAMETRU (WYRAŻENIE LAMBDA)
Na początek postać, z której najczęściej będziesz korzystać. Osadzenie funkcji prosto w miejsce parametru, którego oczekuje metoda "forEach". Trzymając się tego samego przykładu z wypisywaniem każdego elementu w konsoli, wyżej zdefiniowaną pętlę "for" możemy to zamienić na to 👇:
array.forEach(function(element) {
console.log(element);
});albo jeszcze prościej:
array.forEach(element => console.log(element));"Wplecenie" funkcji bezpośrednio w miejsce innej funkcji, nazywamy wyrażeniem lambda. Korzystaj z tej postaci wtedy, gdy dana funkcja jest potrzebna tylko w tym jednym miejscu na cały kod źródłowy i nie potrzeba użyć jej gdziekolwiek indziej ℹ️.
FUNKCJA Z JEDNYM PARAMETREM ZDEFINIOWANA NA ZEWNĄTRZ
Drugi przykład: funkcja o tej samej postaci z tą samą instrukcją, tylko zdefiniowana na zewnątrz 👇:
function writeElement(element) {
console.log(element);
}
array.forEach(writeElement);I taki zapis będzie miał sens, gdy ta sama funkcja będzie potrzebna jeszcze w paru innych miejscach w kodzie.
Zauważ, że w miejsce parametru metody "forEach", wprowadzona została sama nazwa funkcji bez żadnych nawiasów okrągłych! Podajemy samą nazwę, czyli referencję do funkcji, ponieważ my przekazujemy samą informację co ma się wywołać, a wywoływanie odbywa się już w środku 🔥.
FUNKCJA ZE WSZYSTKIMI PARAMETRAMI ZDEFINIOWANA NA ZEWNĄTRZ
Ostatni przykład: ta sama funkcja, tylko akceptująca wszystkie 3 parametry, zamiast jednego:
function writeElement(element, index, array) {
console.log("Element: " + element);
console.log("Indeks: " + index);
console.log("Struktura: " + array);
}
array.forEach(writeElement);Gdy to sobie odpalisz, to zobaczysz, że oprócz wypisywania każdego elementu po kolei, pojawią się jeszcze dwie informacje:
- aktualna wartość "wbudowanego" licznika indeksu (przypominam, że zaczyna liczyć od zera),
- aktualny stan obiektu, w którym wywołano metodę "forEach".
Nie musisz wszystkich definiować na wyrost. Korzystaj z jednego, z dwóch albo ze wszystkich tylko wtedy, gdy są Ci naprawdę potrzebne 😉!
RÓŻNICE WZGLĘDEM INNYCH (NIEKTÓRYCH) JĘZYKÓW
Żeby było kolorowo, skonfrontujemy sobie tablicę występującą w języku JavaScript z tym, jak wygląda tablica w większości innych równie często wykorzystywanych języków. Głównie będę mieć na myśli takie języki, jak 👇:
1. ROZCIĄGLIWOŚĆ
W języku JavaScript, do tablicy możemy swobodnie dodawać i usuwać elementy. Tyle tylko, że to nie leży w naturze tablicy!
Należy wiedzieć, że to jest struktura statyczna ℹ️. Oznacza to, że definicja jej wielkości określana jest na poziomie pisania kodu i zwykle jest później niezmienna. W celu późniejszego dostosowania wielkości, trzeba stworzyć całkowicie nową tablicę. Tablica w języku JavaScript to nie jest zwyczajna tablica. To jest w gruncie rzeczy "tablica na sterydach" 😄. Żeby rozwinąć tę myśl, muszę otworzyć nowy akapit. Dawać mi tu nowy nagłówek 😁!
O CO TU TAK NAPRAWDĘ CHODZI?
JavaScript lubi mocno odstawać od ogólnie przyjętych norm, co napisałem już dużo wcześniej 😅. Tablica jaka tu występuje jest "rozciągliwa", więc mimo tego, że sama w sobie nie posiada możliwości "ustawiania" długości po inicjalizacji jak się chce, to jest w stanie to robić.
To tak naprawdę złamanie jednej z przyjętych norm jakie panują od czasów języka C, choć JavaScript wcale nie jest osamotniony (język Lua też posiada taką rozciągliwą tablicę). To nie są żadne sztuczki Merlina, tylko charakterystyczna cecha jednej ze struktur danych, jaką jest lista dynamiczna 😱.
Lista dynamiczna przechowuje dane tak samo jak tablica (tylko już bez indeksów), z tym, że jej rozmiar może się dowolnie zmieniać podczas działania programu ℹ️! Nawet możesz nie mieć pojęcia jak bardzo JavaScript wyręcza Cię od brudnej roboty 💦. W warunkach języka C, trzeba umieć operować na wskaźnikach, bo tylko one otwierają drogę do modyfikacji rozmiarów listy w trakcie działania programu. A tu chlast 🔪 - jedna instrukcja i już niczym nie musimy się przejmować 😀.
Wniosek: tablica w języku JavaScript to tak naprawdę lista dynamiczna mogąca elastycznie zmieniać rozmiary w trakcie działania programu (metody "push" i "pop").
2. WIELE TYPÓW W JEDNYM MIEJSCU
Oto drugie odstępstwo od normy.
Przyjęło się w wielu językach o silnej typizacji, że tablica ma prawo przechowywać obiekty tylko JEDNEGO typu i to w dodatku ściśle określonego podczas jej deklaracji. JavaScript nie narzuca takiego obowiązku i umożliwia wstawianie wszystkiego jak leci. Czy to łańcuchy znaków, czy wartości logiczne, wolno Ci umieszczać wszystko bez ograniczeń 🙂.
Natomiast warto sobie zadać pytanie czy aby na pewno to dobrze, że język pozwala na coś takiego. Jak potem zaprogramujesz pętlę mającą obsłużyć każdy bez wyjątku argument? Wykrywanie typu elementu, dobieranie metody, ewentualne jakieś weryfikacje, masakra 😰! Ja bym sobie nie zwalał takich kłopotów na kark i mimo wszystko trzymał się jednego typu danych. Obiekty do obiektów, łańcuchy do łańcuchów i pętla na każdy ze zbiorów z osobna. Tobie radzę robić tak samo 🫵.
Wniosek: tablica w języku JavaScript potrafi przechowywać wiele wartości RÓŻNYCH typów (choć NIE POLECAM stosowania tej praktyki).
Tablica będąca tak naprawdę listą dynamiczną typu obiektowego, to miejsce do składowania wielu elementów różnych typów.
"const" i w tym przypadku nie stanowi ochrony przed modyfikacją argumentów tablicy - on jedynie zabroni przypisywania nowej wartości (poprzez znak równości). To samo, co było przy obiekcie.
Tyle informacji wystarczy, aby wiedzieć do czego jest zdolna tablica w języku JavaScript 📖. Sporo tego 😄!