Praca z tablicami w JavaScript rzadko wymaga już pisania pętli for. Język oferuje teraz ekspresywne narzędzia, które zamieniają zagnieżdżone, nieczytelne dane w czyste, czytelne transformacje. Ten przewodnik kontynuuje to, co zaczęły podstawy – skupiając się na spłaszczaniu (flattening), redukowaniu (reducing), zapytaniach oraz nowszych wzorcach niezmiennych (immutable), które pozwalają zachować oryginalne dane nienaruszonymi.
Spłaszczanie zagnieżdżonych struktur za pomocą flat() i flatMap()
Dane w rzeczywistych zastosowaniach uwielbiają zagnieżdżenia. Pobierasz rekordy z API, parsujesz plik CSV lub czytasz plik konfiguracyjny i często kończysz z tablicami wewnątrz tablic. flat() służy do redukowania tych warstw do pojedynczej, liniowej listy.
Wywołaj flat() na zagnieżdżonej tablicy, a domyślnie spłaszczy ona jeden poziom głębokości:
const nested = [[1, 2], [3, 4]];
const flat = nested.flat();
// [1, 2, 3, 4]
Oryginalna tablica nested pozostaje dokładnie taka sama. Jeśli zagnieżdżenie jest głębsze, przekaż argument głębokości, np. flat(2) lub nawet flat(Infinity), gdy nie wiesz, ile warstw istnieje. To lepsze niż ręczne pisanie rekurencyjnych funkcji pomocniczych.
Gdy musisz przekształcić elementy oraz spłaszczyć wynik, flatMap() robi obie te rzeczy w jednym przebiegu. Wykonuje funkcję mapującą na każdym elemencie, a następnie spłaszcza zwrócone tablice o jeden poziom. Wykonanie tego za pomocą oddzielnych wywołań .map().flat() działa, ale flatMap() pomija pośrednią tablicę, co oszczędza pamięć i drugi przebieg. Klasycznym przypadkiem użycia jest rozbicie tablicy zdań na pojedyncze słowa:
const lines = ['hello world', 'foo bar'];
const words = lines.flatMap(line => line.split(' '));
// ['hello', 'world', 'foo', 'bar']
Filtrowanie i mapowanie: Poznaj różnicę
filter() tworzy nową tablicę zawierającą tylko te elementy, które przejdą Twój test. Funkcja zwrotna (callback) powinna zwrócić wartość truthy, aby zachować element, lub falsy, aby go odrzucić. Nigdy nie mutuje ona tablicy źródłowej.
const users = [
{ name: 'Ajay', active: true },
{ name: 'Bina', active: false }
];
const activeUsers = users.filter(u => u.active);
Łatwo pomylić filter() z map(), ponieważ oba zwracają nowe tablice i przyjmują funkcje zwrotne. Różnica tkwi w kontrakcie. map() zwraca nową tablicę o tej samej długości. Przekształć każdy element, wykonaj sprawdzenie warunkowe, a zobaczysz true lub false w każdym pojedynczym miejscu, ponieważ map() nigdy nie odrzuca elementów – on je tylko zmienia. filter() z kolei zwraca potencjalnie krótszą tablicę zawierającą tylko te elementy, które przejdą Twój test. Jeśli chcesz usunąć dane, sięgnij po filter(). Jeśli chcesz przekształcić każdy element danych, zachowując ich liczbę, użyj map().
Redukowanie tablic za pomocą reduce()
Podczas gdy map() i filter() zwracają tablice, reduce() sprowadza całą listę do jednej wartości. Przechodzi od lewej do prawej, niosąc akumulator przez każdy krok.
const totals = [10, 20, 30];
const sum = totals.reduce((acc, current) => acc + current, 0);
// 60
Drugi argument — 0 w powyższym przykładzie — to początkowa wartość akumulatora. Jeśli go pominiesz, JavaScript użyje pierwszego elementu jako punktu startowego, rozpoczynając iterację od indeksu 1. Może to być mylące, gdy tablica jest pusta, ponieważ wywołanie reduce() na pustej tablicy bez wartości początkowej rzuca błąd TypeError.
reduce nie służy tylko do arytmetyki. Możesz go użyć do budowania tabel częstotliwości, łączenia obiektów, znajdowania wartości maksymalnych lub przepuszczania danych przez wiele transformacji. Ponieważ jest tak elastyczne, warto trzymać funkcję zwrotną krótką i jasno nazywać zmienne. Gęsty blok reduce() szybko staje się trudniejszy do odczytania niż pętla, którą zastąpił.
Zapytania logiczne: every() i some()
Czasami nie potrzebujesz nowej tablicy; potrzebujesz odpowiedzi typu tak lub nie na temat kolekcji. every() zwraca true tylko wtedy, gdy wszystkie elementy spełniają Twój warunek. Jeśli pierwszy element nie spełni warunku, metoda natychmiast przerywa działanie i zwraca false.
const scores = [85, 92, 78];
const allPassed = scores.every(s => s >= 50);
some() jest lustrzanym odbiciem. Zwraca true, jeśli przynajmniej jeden element przejdzie test. Jeśli znajdzie dopasowanie, natychmiast przerywa działanie.
const hasPerfectScore = scores.some(s => s === 100);
Metody te zastępują ręczne pętle w procesach walidacji. Musisz potwierdzić, czy każde pole formularza jest wypełnione? Użyj every(). Chcesz wiedzieć, czy jakikolwiek użytkownik ma uprawnienia administratora? Użyj some().
Inspekcja i aktualizacja bez mutacji
Nowoczesny JavaScript silnie opiera się na niezmienności (immutability). Te metody pomagają inspekcjonować lub aktualizować tablice bez dotykania oryginału.
keys() zwraca obiekt iteratora zawierający indeksy tablicy. entries() zwraca iterator par [index, value]. Świetnie sprawdzają się w pętlach for...of, gdy chcesz użyć czystej destrukturyzacji:
const fruits = ['apple', 'banana'];
for (const [index, fruit] of fruits.entries()) {
console.log(index, fruit);
}
with() jest nowszą funkcją języka, która zastępuje element pod konkretnym indeksem i zwraca zupełnie nową tablicę. Odpowiada na tę samą potrzebę co przypisanie za pomocą nawiasów kwadratowych — arr[index] = value — z tą różnicą, że pozostawia tablicę źródłową bez zmian. W frameworkach takich jak React, gdzie bezpośrednia mutacja przerywa wykrywanie zmian, with() jest bezpieczniejszą ścieżką:
const original = [10, 20, 30];
const updated = original.with(1, 99);
// original: [10, 20, 30]
// updated: [10, 99, 30]
Spread i Rest: dwa kierunki działania ...
Składnia spread i parametr rest korzystają z tych samych trzech kropek, ale przemieszczają dane w przeciwnych kierunkach.
Użyj operatora spread, aby rozbić tablicę na pojedyncze elementy. Jest to najczystszy sposób na łączenie tablic lub ich klonowanie:
const front = [1, 2];
const back = [3, 4];
const merged = [...front, ...back];
const copy = [...merged];
Możesz też łatwo dorzucić dodatkowe elementy: [0, ...front, 99, ...back].
Parametr rest gromadzi wiele elementów w jedną tablicę. Wewnątrz sygnatury funkcji zbiera on wszystkie pozostałe argumenty:
function logFirst(first, ...rest) {
console.log('First:', first);
console.log('Rest:', rest);
}
logFirst('a', 'b', 'c');
// First: a
// Rest: ['b', 'c']
Razem radzą sobie z powszechnym zadaniem przyjmowania zmiennej liczby argumentów, przetwarzania ich jako tablicy, a następnie ponownego ich rozpakowywania do wywołań funkcji lub nowych struktur danych.
Kluczowy wniosek
Celem tych metod nie jest imponowanie współpracownikom kodem w jednej linii. Chodzi o to, aby Twoje intencje były oczywiste. Gdy inny programista widzi filter(), wie, że usuwasz elementy. Gdy widzi reduce(), wie, że agregujesz dane. Gdy widzi with(), wie, że oryginalna tablica wciąż gdzieś istnieje w nienaruszonym stanie. Opanowanie tych narzędzi oznacza pisanie kodu, który sam się tłumaczy, co oszczędza znacznie więcej czasu niż jakakolwiek sprytna pętla.
