W Parku Jurajskim dane o dinozaurach przyrastają szybciej niż same dinozaury - gatunek, wymiary, dieta, poziom agresji, terminy badań weterynaryjnych. Kiedy to wszystko wyląduje w jednej tablicy, pojedynczy wpis przestaje nas obchodzić. Chcemy odwiedzić każdy element, przerobić go na coś innego, wyłowić tylko te okazy, które wymagają reakcji, albo sprowadzić całe stado do jednej liczby na tablicy w centrum kontroli. JavaScript ma na to osobną rodzinę metod iteracyjnych i każda z nich ma inną specjalność, @name. Trafny wybór to różnica między czytelnym raportem bezpieczeństwa a kodem, do którego nikt nie chce wracać.
Metoda
forEach uruchamia funkcję raz dla każdego elementu tablicy. To najprostszy sposób iteracji - sięgamy po niego wtedy, gdy chcemy z elementami coś zrobić: wypisać je, zapisać do dziennika, wysłać do stacji monitoringu. Nowa tablica nie jest nam wtedy do niczego potrzebna. Funkcja, którą przekazujemy, dostaje bieżący element, a gdy zajdzie taka potrzeba - również jego indeks i całą tablicę.1// Tablica zawierajaca obiekty reprezentujace dinozaury w naszym parku
2const dinosaurs = [
3 { name: "Tyrannosaurus", diet: "carnivore", aggressionLevel: 10, dangerous: true },
4 { name: "Triceratops", diet: "herbivore", aggressionLevel: 4, dangerous: false },
5 { name: "Velociraptor", diet: "carnivore", aggressionLevel: 9, dangerous: true },
6 { name: "Stegosaurus", diet: "herbivore", aggressionLevel: 2, dangerous: false },
7 { name: "Brachiosaurus", diet: "herbivore", aggressionLevel: 1, dangerous: false }
8];
9
10// Uzywamy forEach do wyswietlenia nazw wszystkich dinozaurow
11console.log("Lista dinozaurow w parku:");
12dinosaurs.forEach(function(dino) {
13 console.log(dino.name);
14});
15
16// Mozemy rowniez uzyc funkcji strzalkowej (arrow function) dla bardziej zwiezlego zapisu
17console.log("Lista dinozaurow z poziomem agresji:");
18dinosaurs.forEach(dino => {
19 console.log(`${dino.name}: Poziom agresji ${dino.aggressionLevel}/10`);
20});Metoda
forEach nie oddaje nam niczego - a dokładniej zwraca undefined - dlatego używamy jej tylko wtedy, gdy zależy nam na samym działaniu na elementach, a nie na wyniku do dalszego przetworzenia. Warto od razu wiedzieć, czego forEach nie potrafi: nie da się jej zatrzymać w połowie. break w środku funkcji zwrotnej jest niedozwolone, a return kończy wyłącznie bieżące wywołanie i przechodzimy prosto do kolejnego dinozaura. Gdy naprawdę potrzebujemy przerwać przebieg wcześniej, robotę biorą na siebie find, some, every albo klasyczna pętla for. Przyjrzyj się jeszcze polu dangerous w naszych kartotekach - to flaga, którą regulamin parku podnosi drapieżnikom pod wzmożonym nadzorem. Wrócimy do niej pod koniec lekcji.Metoda
map tworzy nową tablicę z wynikami wywołania przekazanej funkcji dla każdego elementu tablicy wejściowej. Jest niezwykle przydatna wszędzie tam, gdzie przekształcamy dane z jednej postaci na drugą - z pełnych kartotek na zwykłą listę nazw albo na krótkie podsumowania bezpieczeństwa, które strażnicy faktycznie zdążą przeczytać przed obchodem.1// Uzywamy map do stworzenia nowej tablicy zawierajacej tylko nazwy dinozaurow
2const dinoNames = dinosaurs.map(dino => dino.name);
3console.log("Nazwy dinozaurow:", dinoNames);
4// Wynik: ["Tyrannosaurus", "Triceratops", "Velociraptor", "Stegosaurus", "Brachiosaurus"]
5
6// Mozemy tez tworzyc bardziej zlozone transformacje
7const dinoSummaries = dinosaurs.map(dino => {
8 return {
9 name: dino.name,
10 dangerLevel: dino.diet === "carnivore" ? "Wysoki" : "Niski",
11 warning: dino.aggressionLevel > 7 ? "Zachowac szczegolna ostroznosc!" : "Standardowe procedury bezpieczenstwa"
12 };
13});
14
15console.log("Podsumowanie bezpieczenstwa dinozaurow:", dinoSummaries);W przeciwieństwie do
forEach, metoda map zawsze oddaje nową tablicę o tej samej długości co oryginalna, wypełnioną przekształconymi elementami. I na tym właśnie polega kluczowa różnica między nimi: map() zwraca nową tablicę, a forEach() zwraca undefined. To nie jest kwestia wydajności - map() nie jest szybsze, obie metody odwiedzają dokładnie tyle samo elementów, każdy po jednym razie. To także nie jest kwestia przerywania: ani forEach(), ani map() nie da się zatrzymać w trakcie, więc twierdzenie, że tylko jedną z nich można zatrzymać, jest po prostu nieprawdziwe. Nie działają też identycznie - wywołanie forEach w nadziei na odzyskanie tablicy zostawi Cię z undefined w ręku. Po map sięgamy, gdy potrzebujemy wyniku, po forEach - gdy liczy się wyłącznie efekt uboczny.Metoda
filter tworzy nową tablicę zawierającą tylko te elementy, które spełniają zadany warunek. To idealne narzędzie, gdy z większej kolekcji trzeba wyodrębnić podzbiór: mięsożercy spośród wszystkich dinozaurów, wybiegi do inspekcji spośród wszystkich wybiegów, kartoteki do przekazania weterynarzowi spośród wszystkich kartotek.1// Filtrujemy, aby uzyskac tylko dinozaury miesozerne
2const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
3console.log("Miesozerne dinozaury:", carnivores);
4
5// Mozemy laczyc warunki, aby tworzyc bardziej zlozone filtry
6const dangerousDinos = dinosaurs.filter(dino => {
7 return dino.diet === "carnivore" && dino.aggressionLevel > 7;
8});
9console.log("Najbardziej niebezpieczne dinozaury:", dangerousDinos);
10
11// Mozemy tez laczyc filter z map, aby najpierw odfiltrowac, a potem przeksztalcic dane
12const safetyWarnings = dinosaurs
13 .filter(dino => dino.aggressionLevel > 5)
14 .map(dino => `UWAGA: ${dino.name} ma wysoki poziom agresji!`);
15
16console.log("Ostrzezenia bezpieczenstwa:", safetyWarnings);Metoda
filter zwraca nową tablicę, która bywa krótsza od oryginalnej, bo trafiają do niej wyłącznie elementy, które przeszły przez "filtr" - czyli te, dla których funkcja testująca zwróciła true. Sama tablica dinosaurs wychodzi z tego bez zadraśnięcia: podobnie jak map, filter nigdy nie modyfikuje kolekcji, na której ją wywołujemy, a jedynie o niej raportuje. Zapamiętaj też układ ostatniego przykładu, bo za chwilę okaże się kluczowy: najpierw filtrowanie, dopiero potem transformacja. Ta kolejność nie jest kwestią gustu i zaraz zobaczysz dlaczego.Metoda
reduce jest najpotężniejsza z metod iteracyjnych, ale też najbardziej wymagająca. Służy do redukcji tablicy do pojedynczej wartości, na przykład sumy. Wykonuje funkcję na każdym elemencie, przekazując dalej wynik z poprzedniej iteracji: w każdym kroku dostajesz to, co udało się nazbierać do tej pory, oraz bieżący element, a oddajesz nową wartość akumulatora.1// Obliczamy sume poziomow agresji wszystkich dinozaurow
2const totalAggressionLevel = dinosaurs.reduce((total, dino) => {
3 return total + dino.aggressionLevel;
4}, 0); // 0 to wartosc poczatkowa
5
6console.log("Calkowity poziom agresji w parku:", totalAggressionLevel);
7
8// Mozemy uzyc reduce do tworzenia bardziej zlozonych struktur, np. obiektu grupujacego dinozaury wedlug diety
9const dinosByDiet = dinosaurs.reduce((groups, dino) => {
10 // Jesli grupa dla tej diety jeszcze nie istnieje, tworzymy ja
11 if (!groups[dino.diet]) {
12 groups[dino.diet] = [];
13 }
14
15 // Dodajemy dinozaura do odpowiedniej grupy
16 groups[dino.diet].push(dino.name);
17
18 return groups;
19}, {}); // {} to wartosc poczatkowa (pusty obiekt)
20
21console.log("Dinozaury pogrupowane wedlug diety:", dinosByDiet);
22// Wynik: { "carnivore": ["Tyrannosaurus", "Velociraptor"], "herbivore": ["Triceratops", "Stegosaurus", "Brachiosaurus"] }Metoda
reduce przyjmuje dwa parametry:Nazwa tej metody bywa myląca, więc uporządkujmy, do czego
reduce() nie służy. Nie usuwa elementów z tablicy - obie powyższe operacje przeczytały nasze kartoteki i zostawiły je w spokoju. Nie zmniejsza też rozmiaru tablicy: po wykonaniu obu wywołań dinosaurs nadal liczy pięć wpisów, co do jednego. Nie sortuje niczego malejąco - porządkowaniem elementów zajmuje się sort(), którą poznasz za moment. Słowo "redukcja" oznacza tu dokładnie jedno: sprowadzenie całej tablicy do pojedynczej wartości, czy będzie to liczba, tekst, czy obiekt grupujący pokroju dinosByDiet.Metoda
find zwraca pierwszy element tablicy, który spełnia podany warunek. Po nią sięgamy wtedy, gdy szukamy konkretnej kartoteki, a nie całej listy: strażnik, który prosi "otwórz mi teczkę Stegozaura", nie oczekuje tablicy, tylko jednego dinozaura.1// Szukamy dinozaura o okreslonej nazwie
2const stegosaurus = dinosaurs.find(dino => dino.name === "Stegosaurus");
3console.log("Informacje o Stegosaurusie:", stegosaurus);
4
5// Szukamy pierwszego dinozaura miesozernego o agresji wiekszej niz 8
6const dangerousCarnivore = dinosaurs.find(dino => {
7 return dino.diet === "carnivore" && dino.aggressionLevel > 8;
8});
9console.log("Pierwszy niebezpieczny miesozerca:", dangerousCarnivore); // Tyrannosaurus
10
11// findIndex dziala tak samo, ale zwraca pozycje zamiast kartoteki
12const stegoIndex = dinosaurs.findIndex(dino => dino.name === "Stegosaurus");
13console.log("Pozycja Stegosaurusa:", stegoIndex); // 3W przeciwieństwie do
filter, który zwraca tablicę wszystkich pasujących elementów, find oddaje tylko pierwsze trafienie i natychmiast przerywa przeszukiwanie - pozostałe dinozaury nie zostaną nawet obejrzane. Jeśli nic nie pasuje, dostajemy undefined. Jej bliźniacza siostra findIndex kieruje się tą samą logiką, ale zamiast elementu podaje jego indeks, a przy braku dopasowania zwraca -1. Bywa to nieocenione, gdy zaraz potem trzeba podmienić kartotekę leżącą na tej pozycji.Metody
some i every zwracają wartość boolowską i służą do sprawdzania, czy elementy tablicy spełniają określone kryteria. Żadna z nich nie buduje nowej tablicy ani nie oddaje elementów - odpowiadają po prostu true albo false na pytanie o całą kolekcję, co jest dokładnie tym, czego potrzebuje alarm albo kontrola bezpieczeństwa przed otwarciem parku.Metoda
some sprawdza, czy przynajmniej jeden element tablicy spełnia podany warunek. Gdy tylko natrafi na pasujący element, kończy przebieg i zwraca true - dalsze szukanie niczego by już nie zmieniło.1// Sprawdzamy, czy w parku sa jakies niebezpieczne dinozaury
2const hasHighAggressionDinos = dinosaurs.some(dino => dino.aggressionLevel > 8);
3console.log("Czy park ma dinozaury o wysokiej agresji?", hasHighAggressionDinos); // true
4
5// Sprawdzamy, czy w parku sa jakies dinozaury wszystkozerne
6const hasOmnivores = dinosaurs.some(dino => dino.diet === "omnivore");
7console.log("Czy park ma dinozaury wszystkozerne?", hasOmnivores); // falsePierwsze wywołanie zwraca
true, bo Tyranozaur przekracza próg już przy pierwszym sprawdzanym elemencie i dalej nie ma po co iść. Drugie musi przejść całą listę, zanim wyda werdykt: żadna kartoteka nie ma diety "omnivore", więc odpowiedź brzmi false. To właśnie ta rodzina metod potrafi przerwać przebieg w połowie - o forEach i map pamiętaj, że zawsze idą do samego końca tablicy.Metoda
every sprawdza, czy wszystkie elementy tablicy spełniają podany warunek. Działa symetrycznie do some: zatrzymuje się na pierwszym elemencie, który obleje test, i od razu zwraca false.1// Sprawdzamy, czy wszystkie dinozaury maja przypisany poziom agresji
2const allHaveAggressionLevel = dinosaurs.every(dino => dino.aggressionLevel !== undefined);
3console.log("Czy wszystkie dinozaury maja przypisany poziom agresji?", allHaveAggressionLevel); // true
4
5// Sprawdzamy, czy wszystkie dinozaury sa roslinozerne
6const allHerbivores = dinosaurs.every(dino => dino.diet === "herbivore");
7console.log("Czy wszystkie dinozaury sa roslinozerne?", allHerbivores); // falsePierwsza kontrola wypada pomyślnie: każda z pięciu kartotek ma uzupełnione pole
aggressionLevel, więc every przechodzi całą tablicę i zwraca true. Druga wykłada się już na pierwszym wpisie - Tyranozaur jest mięsożercą - i zwraca false, nie zaglądając do pozostałych. Najłatwiej zapamiętać tę parę jako dwa pytania zadawane na głos: some pyta "czy jest przynajmniej jeden?", a every pyta "czy wszystkie?".Metoda
sort porządkuje elementy tablicy i zwraca posortowaną tablicę. Domyślnie porównuje je jak ciągi znaków, co przy liczbach potrafi zaskoczyć - tablica [10, 9, 100] wychodzi z takiego sortowania jako [10, 100, 9], bo znak po znaku "1" wypada przed "9". Właśnie dlatego niemal zawsze przekazujemy jej własną funkcję porównującą.1// Sortujemy dinozaury wedlug nazwy (alfabetycznie)
2const dinosSortedByName = [...dinosaurs].sort((a, b) => {
3 return a.name.localeCompare(b.name);
4});
5console.log("Dinozaury posortowane alfabetycznie:", dinosSortedByName);
6
7// Sortujemy dinozaury wedlug poziomu agresji (od najmniej do najbardziej agresywnych)
8const dinosSortedByAggression = [...dinosaurs].sort((a, b) => {
9 return a.aggressionLevel - b.aggressionLevel;
10});
11console.log("Dinozaury posortowane wedlug agresji (rosnaco):", dinosSortedByAggression);
12
13// Sortujemy dinozaury wedlug poziomu agresji (od najbardziej do najmniej agresywnych)
14const mostDangerousFirst = [...dinosaurs].sort((a, b) => {
15 return b.aggressionLevel - a.aggressionLevel;
16});
17console.log("Dinozaury posortowane wedlug agresji (malejaco):", mostDangerousFirst);Kopię tablicy tworzymy operatorem spread (
...) nie bez powodu: sort modyfikuje oryginał, a my chcemy zachować pierwotną kolejność rejestru parku. To wielki wyjątek wśród poznanych dotąd metod - map, filter i reduce zostawiają źródło nietknięte, sort nie. O kierunku decyduje funkcja porównująca: wynik ujemny stawia a przed b, dlatego a.aggressionLevel - b.aggressionLevel układa okazy rosnąco, a zamiana operandów miejscami daje porządek malejący. I to sort, wyłącznie sort, odpowiada w JavaScripcie za sortowanie rosnące i malejące.Jedną z najpotężniejszych cech tych metod jest możliwość łączenia ich w "łańcuchy", dzięki czemu złożoną operację na danych zapisujemy jako jedno czytelne wyrażenie. Działa to dlatego, że
filter, map i sort oddają tablicę, więc kolejną metodę wywołujemy wprost na wyniku poprzedniej. Najczęstszy wzorzec w parku składa się z dwóch kroków: filtrowania, a zaraz po nim transformacji.1// Zrodlo, potem filtrowanie, potem transformacja
2const dangerousNames = dinosaurs
3 .filter(d => d.dangerous) // 1. zostawiamy tylko kartoteki z flaga dangerous
4 .map(d => d.name); // 2. z kazdej ocalalej kartoteki bierzemy sama nazwe
5
6console.log("Nazwy niebezpiecznych dinozaurow:", dangerousNames);
7// Wynik: ["Tyrannosaurus", "Velociraptor"]Ten łańcuch czytamy od lewej do prawej, a kolejność jego ogniw jest zawsze taka sama: najpierw źródło danych
, potem filtrowanie dinosaurs
, a na końcu transformacja .filter(d => d.dangerous)
. Ta kolejność nie podlega negocjacjom, @name. Gdyby zamienić dwa ostatnie ogniwa miejscami, .map(d => d.name)
map oddałoby tablicę zwykłych napisów, a filter szukałoby potem właściwości dangerous na tekście - wszędzie wypadłoby undefined i w wyniku dostalibyśmy pustą tablicę. Filtrujemy, dopóki mamy jeszcze pełne obiekty, przekształcamy dopiero potem. Gdy ten odruch wejdzie Ci w krew, łańcuch możesz wydłużać dowolnie i wplatać w niego sort albo reduce.1// Znajdzmy nazwy wszystkich dinozaurow roslinozernych, posortowane alfabetycznie
2const sortedHerbivoreNames = dinosaurs
3 .filter(dino => dino.diet === "herbivore") // Najpierw filtrujemy
4 .map(dino => dino.name) // Potem transformujemy do tablicy nazw
5 .sort(); // Na koncu sortujemy alfabetycznie
6
7console.log("Roslinozercy (alfabetycznie):", sortedHerbivoreNames);
8// Wynik: ["Brachiosaurus", "Stegosaurus", "Triceratops"]
9
10// Obliczmy sredni poziom agresji miesozercow
11const averageCarnivoreAggression = dinosaurs
12 .filter(dino => dino.diet === "carnivore") // Najpierw filtrujemy tylko miesozercow
13 .reduce((sum, dino, index, array) => { // Potem redukujemy do sredniej
14 sum += dino.aggressionLevel; // Dodajemy poziom agresji do sumy
15
16 // Jesli to ostatni element, dzielimy przez liczbe elementow
17 if (index === array.length - 1) {
18 return sum / array.length;
19 }
20
21 return sum; // Zwracamy sume czesciowa
22 }, 0);
23
24console.log("Sredni poziom agresji miesozercow:", averageCarnivoreAggression); // 9.5Drugi przykład pokazuje, jak elastyczna potrafi być
reduce. Funkcja redukcji dostaje nie tylko akumulator i bieżący element, ale też indeks oraz całą przetwarzaną tablicę, dzięki czemu potrafimy rozpoznać ostatnią iterację i w odpowiednim momencie podzielić zebraną sumę. Mięsożerców mamy dwóch, ich poziomy agresji to 10 i 9, więc na wyjściu dostajemy 9.5 - jedną liczbę wyciśniętą z całej tablicy kartotek, czyli dokładnie to, do czego reduce została stworzona.Metody iteracyjne tablic w JavaScript są nieocenionym narzędziem przy pracy z kolekcjami danych. Każda ma swoją specjalizację:
undefined)"Metody iteracyjne to poranna odprawa w parku" - mawia Dr. Rex. "
forEach przechodzi codzienną listę kontrolną, filter wyławia tylko te okazy, które wymagają uwagi, map przerabia je na raport, a reduce daje centrum kontroli jedną liczbę, na której da się oprzeć decyzję. Każde narzędzie ma swoje zadanie!"W naszym Parku Jurajskim te metody są niezbędne do efektywnego zarządzania danymi o dinozaurach, monitorowania bezpieczeństwa i podejmowania decyzji opartych na danych. Zapamiętaj przede wszystkim jedno: metodę dobieramy według tego, co chcemy dostać - efekt uboczny, nową tablicę, podzbiór czy pojedynczą wartość - a reszta kodu napisze się już niemal sama.