Kurs JavaScript i TypeScript · Moduł 5: Zaawansowany JavaScript
Wyższe metody tablic: map, filter, reduce
W tej lekcji7
Weterynarz parku prosi o raport: imiona wszystkich drapieżników, ich łączny poziom zagrożenia i średni wiek stada. Zwykłą pętlą for da się to zrobić, ale każda odpowiedź to kilka linijek liczników, warunków i tymczasowych tablic. Teraz gdy znasz już podstawowe metody tablic, czas poznać trzy najważniejsze metody wyższego rzędu (higher-order methods), czyli takie, które przyjmują funkcję jako argument. Są one fundamentem funkcyjnego programowania w JavaScript i niezwykle przydatne w pracy z danymi dinozaurów.
map() - transformacja danych
Metoda map() tworzy nową tablicę, wykonując funkcję na każdym elemencie oryginalnej tablicy. Nie modyfikuje oryginalnej tablicy. To, co zwróci funkcja, trafia do wyniku:
1const dinosaurs = [
2 { name: "Blue", age: 4, species: "Velociraptor" },
3 { name: "Rex", age: 12, species: "T-Rex" },
4 { name: "Bronto", age: 8, species: "Brachiosaurus" }
5];
6
7// Wyciągnięcie samych imion
8const names = dinosaurs.map(dino => dino.name);
9console.log(names); // ["Blue", "Rex", "Bronto"]
10
11// Transformacja do nowego formatu
12const reports = dinosaurs.map(dino => ({
13 label: dino.name + " (" + dino.species + ")",
14 ageInMonths: dino.age * 12
15}));
16console.log(reports);
17// [{ label: "Blue (Velociraptor)", ageInMonths: 48 }, ...]Wynik zawsze ma tyle elementów co oryginał: trzy dinozaury na wejściu, trzy raporty na wyjściu. Nawiasy wokół literału obiektu w drugim przykładzie są konieczne, bo bez nich JavaScript uznałby klamry za ciało funkcji strzałkowej.
filter() - filtrowanie danych
Metoda filter() tworzy nową tablicę zawierającą tylko elementy, które spełniają podany warunek (funkcja zwraca true). Działa jak bramka przy wybiegu:
1const dinosaurs = [
2 { name: "Blue", age: 4, diet: "carnivore", dangerLevel: 8 },
3 { name: "Rex", age: 12, diet: "carnivore", dangerLevel: 10 },
4 { name: "Bronto", age: 8, diet: "herbivore", dangerLevel: 2 },
5 { name: "Triki", age: 6, diet: "herbivore", dangerLevel: 3 }
6];
7
8// Filtrowanie drapieżników
9const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
10console.log(carnivores.length); // 2
11
12// Dinozaury o wysokim poziomie zagrożenia
13const dangerous = dinosaurs.filter(dino => dino.dangerLevel >= 8);
14console.log(dangerous.map(d => d.name)); // ["Blue", "Rex"]Przez bramkę przechodzą tylko dinozaury, dla których funkcja zwróciła true. Nie są przy tym kopiowane - to te same obiekty co w oryginale, zebrane w nowej tablicy. Gdy nikt nie spełni warunku, dostaniesz pustą tablicę, a nie undefined.
reduce() - agregacja danych
Metoda reduce() redukuje tablicę do pojedynczej wartości, przetwarzając elementy kolejno z akumulatorem. Akumulator to notes laboranta, a drugi argument reduce() to jego stan początkowy:
1const dinosaurs = [
2 { name: "Blue", age: 4 },
3 { name: "Rex", age: 12 },
4 { name: "Bronto", age: 8 },
5 { name: "Triki", age: 6 }
6];
7
8// Suma wszystkich wieków
9const totalAge = dinosaurs.reduce((sum, dino) => sum + dino.age, 0);
10console.log(totalAge); // 30
11
12// Średni wiek
13const avgAge = totalAge / dinosaurs.length;
14console.log(avgAge); // 7.5Przebieg sumowania wygląda tak: 0 + 4, potem + 12, + 8 i + 6, co daje 30. Wartość startową zawsze podawaj: bez niej reduce() weźmie pierwszy element jako akumulator, czyli tutaj cały obiekt Blue, a zamiast liczby dostaniesz sklejony tekst. Na pustej tablicy bez wartości startowej poleci TypeError.
Akumulator nie musi być liczbą. Może być obiektem, do którego dokładamy dinozaury według diety - tak działa grupowanie:
1const dinosaurs = [
2 { name: "Blue", age: 4, diet: "carnivore" },
3 { name: "Rex", age: 12, diet: "carnivore" },
4 { name: "Bronto", age: 8, diet: "herbivore" },
5 { name: "Triki", age: 6 } // brak wpisu o diecie
6];
7
8// Grupowanie dinozaurów po diecie
9const grouped = dinosaurs.reduce((groups, dino) => {
10 const key = dino.diet || "unknown";
11 if (!groups[key]) groups[key] = [];
12 groups[key].push(dino.name);
13 return groups;
14}, {});
15
16console.log(grouped);
17// { carnivore: ["Blue", "Rex"], herbivore: ["Bronto"], unknown: ["Triki"] }Pusty obiekt jako wartość startowa staje się katalogiem grup, a Triki bez wpisu o diecie trafia do grupy unknown dzięki operatorowi ||. Od ES2024 to samo zrobi krócej Object.groupBy(), które poznasz w lekcji o najnowszych funkcjach JavaScript.
Łączenie metod w pipeline
Prawdziwa moc tych metod ujawnia się, gdy łączymy je w łańcuchy (chaining). Każda metoda dostaje wynik poprzedniej, jak próbka przekazywana między stanowiskami laboratorium:
1const dinosaurs = [
2 { name: "Blue", age: 4, diet: "carnivore", dangerLevel: 8 },
3 { name: "Rex", age: 12, diet: "carnivore", dangerLevel: 10 },
4 { name: "Bronto", age: 8, diet: "herbivore", dangerLevel: 2 },
5 { name: "Triki", age: 6, diet: "herbivore", dangerLevel: 3 }
6];
7
8// Pipeline: filtruj drapieżniki -> wyciągnij imiona -> posortuj
9const dangerousNames = dinosaurs
10 .filter(dino => dino.diet === "carnivore")
11 .map(dino => dino.name)
12 .sort();
13
14console.log(dangerousNames); // ["Blue", "Rex"]
15
16// Całkowity poziom zagrożenia drapieżników
17const totalDanger = dinosaurs
18 .filter(dino => dino.diet === "carnivore")
19 .reduce((sum, dino) => sum + dino.dangerLevel, 0);
20
21console.log(totalDanger); // 18Kolejność ogniw ma znaczenie: najpierw filtrujemy, żeby dalsze kroki pracowały na mniejszej tablicy. Uważaj na sort(), bo w przeciwieństwie do reszty łańcucha mutuje tablicę - tutaj jest bezpieczny, bo sortuje świeży wynik map(). Bez funkcji porównującej sortuje też jak tekst, więc liczby 9, 10 i 1 ułożyłby jako 1, 10, 9.
Szukanie i sprawdzanie
Nie każde pytanie wymaga nowej tablicy. Czasem chcesz znaleźć jednego dinozaura albo tylko sprawdzić warunek - do tego służą metody wyszukiwania i sprawdzania:
1const names = ["Blue", "Rex", "Bronto", "Triki"];
2const levels = [8, 10, 2, 3];
3
4console.log(names.indexOf("Rex")); // 1
5console.log(names.includes("Delta")); // false
6console.log(levels.find(level => level > 5)); // 8 - pierwszy pasujący
7console.log(levels.some(level => level >= 10)); // true - choć jeden
8console.log(levels.every(level => level > 0)); // true - wszystkieindexOf() zwraca pozycję albo -1, includes() odpowiada tak lub nie, a find() oddaje pierwszy pasujący element albo undefined. some() i every() przerywają pracę, gdy tylko znają odpowiedź.
flat() i flatMap() - spłaszczanie
Dane z sektorów często przychodzą jako tablice w tablicach. Metoda flat() z ES2019 spłaszcza je o jeden poziom, a flatMap() łączy map() z takim spłaszczeniem:
1const sectors = [["Blue", "Delta"], ["Rex"], ["Bronto", "Triki"]];
2console.log(sectors.flat()); // ["Blue", "Delta", "Rex", "Bronto", "Triki"]
3
4const zones = [
5 { id: "A", dinosaurs: ["Blue", "Delta"] },
6 { id: "B", dinosaurs: ["Rex"] }
7];
8const everyone = zones.flatMap(zone => zone.dinosaurs);
9console.log(everyone); // ["Blue", "Delta", "Rex"]Zwykłe map() zwróciłoby tu tablicę tablic, a flatMap() od razu jedną listę mieszkańców. Głębsze zagnieżdżenia spłaszczysz, podając głębokość, na przykład flat(2).
Kompozycja funkcji: pipe i compose
Łańcuch filter() i map() wymaga tablicy. Zwykłe funkcje łączy kompozycja: pipe() przepuszcza wartość przez kolejne kroki od lewej do prawej, a zbudujemy ją właśnie z reduce():
1const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
2
3const onlyCarnivores = list => list.filter(dino => dino.diet === "carnivore");
4const toNames = list => list.map(dino => dino.name);
5
6const carnivoreNames = pipe(onlyCarnivores, toNames);
7console.log(carnivoreNames([
8 { name: "Blue", diet: "carnivore" },
9 { name: "Bronto", diet: "herbivore" }
10])); // ["Blue"]Akumulatorem jest tu sama wartość, a każdą funkcję stosujemy do wyniku poprzedniej. compose() robi to samo od prawej do lewej, zwykle z reduceRight(). Kompozycję rozwiniesz w lokacji o programowaniu funkcyjnym.
Tak wygląda ścieżka mistrzostwa w metodach tablic: podstawowe manipulacje (push, pop, shift, unshift), wyszukiwanie (indexOf, includes, find), transformacje (map, filter, reduce), agregacje (every, some, flat), a na szczycie wzorce compose i pipe. W laboratorium poniżej uzupełnisz raporty według komentarzy TODO.
Pamiętaj: map() przekształca, filter() przesiewa, a reduce() zwija całe stado w jeden wynik - razem tworzą linię produkcyjną laboratorium.
Kod do tej lekcji: index.js
1// Wyzsze metody tablic: map, filter, reduce
2
3const dinosaurs = [
4 { name: "Blue", age: 4, diet: "carnivore", dangerLevel: 8 },
5 { name: "Rex", age: 12, diet: "carnivore", dangerLevel: 10 },
6 { name: "Bronto", age: 8, diet: "herbivore", dangerLevel: 2 },
7 { name: "Triki", age: 6, diet: "herbivore", dangerLevel: 3 },
8 { name: "Ptero", age: 5, diet: "carnivore", dangerLevel: 6 }
9];
10
11// === MAP ===
12console.log("=== map() ===");
13const names = dinosaurs.map(dino => dino.name);
14console.log("Imiona:", names);
15
16// TODO: Uzyj map() do stworzenia tablicy obiektow
17// z polami: label (name + species) i ageInMonths (age * 12)
18
19// === FILTER ===
20console.log("\n=== filter() ===");
21const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
22console.log("Drapiezniki:", carnivores.map(d => d.name));
23
24// TODO: Przefiltruj dinozaury o dangerLevel >= 7
25
26// === REDUCE ===
27console.log("\n=== reduce() ===");
28const totalAge = dinosaurs.reduce((sum, dino) => sum + dino.age, 0);
29console.log("Suma wiekow:", totalAge);
30console.log("Sredni wiek:", totalAge / dinosaurs.length);
31
32// TODO: Uzyj reduce() do obliczenia calkowitego poziomu zagrozenia
33
34// === PIPELINE (lancuchowanie metod) ===
35console.log("\n=== Pipeline ===");
36// TODO: Polacz filter -> map -> reduce
37// Filtruj drapiezniki, wyciagnij dangerLevel, zsumuj
38const totalDangerOfCarnivores = dinosaurs
39 .filter(dino => dino.diet === "carnivore")
40 .map(dino => dino.dangerLevel)
41 .reduce((sum, level) => sum + level, 0);
42
43console.log("Calkowite zagrozenie drapieznikow:", totalDangerOfCarnivores);Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Co zwraca metoda map() w JavaScript?
2. Do czego służy metoda filter() w tablicach?
To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Przekształć tablicę Velociraptorów, dodając każdemu właściwość trainingComplete.
- Edytor kodu
Przefiltruj tablicę, aby znaleźć tylko alfa Velociraptory.
- Klikanie w kolejności
Ułóż składnię metody map w poprawnej kolejności:
- Edytor kodu
Oblicz średni wiek stada używając reduce().
- Klikanie w kolejności
Ułóż składnię metody filter w poprawnej kolejności:
- Układanie w pionie
Uporządkuj ścieżkę nauki metod tablicowych od podstawowych do zaawansowanych: