Polski

Odblokuj moc programowania funkcyjnego z tablicami JavaScript. Dowiedz się, jak wydajnie przekształcać, filtrować i redukować dane za pomocą wbudowanych metod.

Mistrzowskie programowanie funkcyjne z tablicami JavaScript

W stale ewoluującym krajobrazie tworzenia stron internetowych, JavaScript wciąż pozostaje kamieniem węgielnym. Podczas gdy paradygmaty programowania obiektowego i imperatywnego od dawna dominują, programowanie funkcyjne (FP) zyskuje na znaczeniu. FP kładzie nacisk na niezmienność, czyste funkcje i kod deklaratywny, prowadząc do bardziej solidnych, łatwych w utrzymaniu i przewidywalnych aplikacji. Jednym z najpotężniejszych sposobów na przyjęcie programowania funkcyjnego w JavaScript jest wykorzystanie natywnych metod tablic.

Ten kompleksowy przewodnik zagłębi się w to, jak możesz wykorzystać moc zasad programowania funkcyjnego, używając tablic JavaScript. Przeanalizujemy kluczowe koncepcje i zademonstrujemy, jak je zastosować, używając metod takich jak map, filter i reduce, zmieniając sposób obsługi manipulacji danymi.

Co to jest programowanie funkcyjne?

Zanim zagłębimy się w tablice JavaScript, pokrótce zdefiniujmy programowanie funkcyjne. U podstaw, FP to paradygmat programowania, który traktuje obliczenia jako ocenę funkcji matematycznych i unika zmiany stanu i zmiennych danych. Kluczowe zasady obejmują:

Przyjęcie tych zasad może prowadzić do kodu, który jest łatwiejszy do przeanalizowania, przetestowania i debugowania, zwłaszcza w złożonych aplikacjach. Metody tablic JavaScript doskonale nadają się do implementacji tych koncepcji.

Moc metod tablic JavaScript

Tablice JavaScript są wyposażone w bogaty zestaw wbudowanych metod, które pozwalają na zaawansowaną manipulację danymi bez uciekania się do tradycyjnych pętli (takich jak for lub while). Metody te często zwracają nowe tablice, promując niezmienność, i akceptują funkcje zwrotne, umożliwiając podejście funkcjonalne.

Przeanalizujmy najbardziej fundamentalne metody tablic funkcyjnych:

1. Array.prototype.map()

Metoda map() tworzy nową tablicę wypełnioną wynikami wywołania podanej funkcji dla każdego elementu w wywoływanej tablicy. Jest idealna do przekształcania każdego elementu tablicy w coś nowego.

Składnia:

array.map(callback(currentValue[, index[, array]])[, thisArg])

Kluczowe cechy:

Przykład: Podwajanie każdej liczby

Wyobraź sobie, że masz tablicę liczb i chcesz utworzyć nową tablicę, w której każda liczba jest podwojona.

const numbers = [1, 2, 3, 4, 5];

// Używanie map do transformacji
const doubledNumbers = numbers.map(number => number * 2);

console.log(numbers); // Wyjście: [1, 2, 3, 4, 5] (oryginalna tablica pozostaje niezmieniona)
console.log(doubledNumbers); // Wyjście: [2, 4, 6, 8, 10]

Przykład: Wyodrębnianie właściwości z obiektów

Typowym przypadkiem użycia jest wyodrębnianie określonych właściwości z tablicy obiektów. Załóżmy, że mamy listę użytkowników i chcemy uzyskać tylko ich imiona.

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const userNames = users.map(user => user.name);

console.log(userNames); // Wyjście: ['Alice', 'Bob', 'Charlie']

2. Array.prototype.filter()

Metoda filter() tworzy nową tablicę ze wszystkimi elementami, które przechodzą test zaimplementowany przez podaną funkcję. Służy do wyboru elementów na podstawie warunku.

Składnia:

array.filter(callback(element[, index[, array]])[, thisArg])

Kluczowe cechy:

Przykład: Filtrowanie liczb parzystych

Filtrujmy tablicę liczb, aby zachować tylko liczby parzyste.

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Używanie filter do wyboru liczb parzystych
const evenNumbers = numbers.filter(number => number % 2 === 0);

console.log(numbers); // Wyjście: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(evenNumbers); // Wyjście: [2, 4, 6, 8, 10]

Przykład: Filtrowanie aktywnych użytkowników

Z naszej tablicy users, filtrujmy użytkowników, którzy są oznaczeni jako aktywni.

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: false }
];

const activeUsers = users.filter(user => user.isActive);

console.log(activeUsers); 
/* Wyjście:
[
  { id: 1, name: 'Alice', isActive: true },
  { id: 3, name: 'Charlie', isActive: true }
]
*/

3. Array.prototype.reduce()

Metoda reduce() wykonuje dostarczoną przez użytkownika funkcję zwrotną „redukującą” na każdym elemencie tablicy, w kolejności, przekazując wartość zwracaną z obliczeń na poprzednim elemencie. Ostateczny wynik uruchomienia reduktora dla wszystkich elementów tablicy to pojedyncza wartość.

Jest to prawdopodobnie najbardziej wszechstronna z metod tablicowych i jest kamieniem węgielnym wielu wzorców programowania funkcyjnego, pozwalając „zredukować” tablicę do pojedynczej wartości (np. suma, iloczyn, liczba lub nawet nowy obiekt lub tablica).

Składnia:

array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

Kluczowe cechy:

Przykład: Sumowanie liczb

Zsumujmy wszystkie liczby w naszej tablicy.

const numbers = [1, 2, 3, 4, 5];

// Używanie reduce do sumowania liczb
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // 0 to initialValue

console.log(sum); // Wyjście: 15

Wyjaśnienie:

Przykład: Grupowanie obiektów według właściwości

Możemy użyć reduce do przekształcenia tablicy obiektów w obiekt, w którym wartości są pogrupowane według określonej właściwości. Pogrupujmy naszych użytkowników według ich stanu `isActive`.

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: false }
];

const groupedUsers = users.reduce((acc, user) => {
  const status = user.isActive ? 'active' : 'inactive';
  if (!acc[status]) {
    acc[status] = [];
  }
  acc[status].push(user);
  return acc;
}, {}); // Pusty obiekt {} to initialValue

console.log(groupedUsers);
/* Wyjście:
{
  active: [
    { id: 1, name: 'Alice', isActive: true },
    { id: 3, name: 'Charlie', isActive: true }
  ],
  inactive: [
    { id: 2, name: 'Bob', isActive: false },
    { id: 4, name: 'David', isActive: false }
  ]
}
*/

Przykład: Zliczanie wystąpień

Policzmy częstotliwość występowania każdego owocu na liście.

const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];

const fruitCounts = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});

console.log(fruitCounts); // Wyjście: { apple: 3, banana: 2, orange: 1 }

4. Array.prototype.forEach()

Chociaż forEach() nie zwraca nowej tablicy i jest często uważana za bardziej imperatywną, ponieważ jej głównym celem jest wykonanie funkcji dla każdego elementu tablicy, wciąż jest to fundamentalna metoda, która odgrywa rolę w wzorcach funkcyjnych, zwłaszcza gdy efekty uboczne są konieczne lub podczas iteracji bez potrzeby przekształconego wyniku.

Składnia:

array.forEach(callback(element[, index[, array]])[, thisArg])

Kluczowe cechy:

Przykład: Rejestrowanie każdego elementu

const messages = ['Hello', 'Functional', 'World'];

messages.forEach(message => console.log(message));
// Wyjście:
// Hello
// Functional
// World

Uwaga: W przypadku transformacji i filtrowania, map i filter są preferowane ze względu na ich niezmienność i charakter deklaratywny. Użyj forEach, gdy chcesz wykonać działanie dla każdego elementu bez zbierania wyników w nową strukturę.

5. Array.prototype.find() i Array.prototype.findIndex()

Metody te są przydatne do lokalizowania określonych elementów w tablicy.

Przykład: Znajdowanie użytkownika

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const bob = users.find(user => user.name === 'Bob');
const bobIndex = users.findIndex(user => user.name === 'Bob');
const nonExistentUser = users.find(user => user.name === 'David');
const nonExistentIndex = users.findIndex(user => user.name === 'David');

console.log(bob); // Wyjście: { id: 2, name: 'Bob' }
console.log(bobIndex); // Wyjście: 1
console.log(nonExistentUser); // Wyjście: undefined
console.log(nonExistentIndex); // Wyjście: -1

6. Array.prototype.some() i Array.prototype.every()

Metody te sprawdzają, czy wszystkie elementy w tablicy przechodzą test zaimplementowany przez podaną funkcję.

Przykład: Sprawdzanie statusu użytkownika

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true }
];

const hasInactiveUser = users.some(user => !user.isActive);
const allAreActive = users.every(user => user.isActive);

console.log(hasInactiveUser); // Wyjście: true (ponieważ Bob jest nieaktywny)
console.log(allAreActive); // Wyjście: false (ponieważ Bob jest nieaktywny)

const allUsersActive = users.filter(user => user.isActive).length === users.length;
console.log(allUsersActive); // Wyjście: false

// Alternatywa używająca every bezpośrednio
const allUsersActiveDirect = users.every(user => user.isActive);
console.log(allUsersActiveDirect); // Wyjście: false

Łańcuchowe metody tablicowe dla złożonych operacji

Prawdziwa moc programowania funkcyjnego z tablicami JavaScript ujawnia się, gdy łańcuchowo łączysz te metody. Ponieważ większość z tych metod zwraca nowe tablice (z wyjątkiem forEach), możesz bezproblemowo przesyłać dane wyjściowe jednej metody do wejścia innej, tworząc eleganckie i czytelne potoki danych.

Przykład: Znajdowanie nazw aktywnych użytkowników i podwajanie ich identyfikatorów

Znajdźmy wszystkich aktywnych użytkowników, wyodrębnijmy ich imiona, a następnie utwórzmy nową tablicę, w której każda nazwa jest poprzedzona numerem reprezentującym jej indeks na *filtrowanej* liście, a ich identyfikatory są podwojone.

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: true },
  { id: 5, name: 'Eve', isActive: false }
];

const processedActiveUsers = users
  .filter(user => user.isActive) // Pobierz tylko aktywnych użytkowników
  .map((user, index) => ({      // Przekształć każdego aktywnego użytkownika
    name: `${index + 1}. ${user.name}`,
    doubledId: user.id * 2
  }));

console.log(processedActiveUsers);
/* Wyjście:
[
  { name: '1. Alice', doubledId: 2 },
  { name: '2. Charlie', doubledId: 6 },
  { name: '3. David', doubledId: 8 }
]
*/

To łańcuchowe podejście jest deklaratywne: określamy kroki (filtrowanie, a następnie mapowanie) bez jawnego zarządzania pętlami. Jest również niezmienne, ponieważ każdy krok generuje nową tablicę lub obiekt, pozostawiając oryginalną tablicę users nietkniętą.

Niezmienność w praktyce

Programowanie funkcyjne w dużej mierze opiera się na niezmienności. Oznacza to, że zamiast modyfikować istniejące struktury danych, tworzysz nowe z pożądanymi zmianami. Metody tablic JavaScript, takie jak map, filter i slice, z natury wspierają to, zwracając nowe tablice.

Dlaczego niezmienność jest ważna?

Gdy musisz wykonać operację, która tradycyjnie zmienia tablicę (np. dodawanie lub usuwanie elementu), możesz osiągnąć niezmienność za pomocą metod takich jak slice, składni spread (...) lub łącząc inne metody funkcyjne.

Przykład: Dodawanie elementu w sposób niezmienny

const originalArray = [1, 2, 3];

// Podejście imperatywne (modyfikuje originalArray)
// originalArray.push(4);

// Podejście funkcyjne z użyciem składni spread
const newArrayWithPush = [...originalArray, 4];
console.log(originalArray); // Wyjście: [1, 2, 3]
console.log(newArrayWithPush); // Wyjście: [1, 2, 3, 4]

// Podejście funkcyjne z użyciem slice i konkatenacji (mniej powszechne obecnie)
const newArrayWithSlice = originalArray.slice(0, originalArray.length).concat(4);
console.log(newArrayWithSlice); // Wyjście: [1, 2, 3, 4]

Przykład: Usuwanie elementu w sposób niezmienny

const originalArray = [1, 2, 3, 4, 5];

// Usuń element na indeksie 2 (wartość 3)

// Podejście funkcyjne z użyciem slice i składni spread
const newArrayAfterSplice = [
  ...originalArray.slice(0, 2),
  ...originalArray.slice(3)
];
console.log(originalArray); // Wyjście: [1, 2, 3, 4, 5]
console.log(newArrayAfterSplice); // Wyjście: [1, 2, 4, 5]

// Używanie filter do usunięcia określonej wartości
const newValueToRemove = 3;
const arrayWithoutValue = originalArray.filter(item => item !== newValueToRemove);
console.log(arrayWithoutValue); // Wyjście: [1, 2, 4, 5]

Najlepsze praktyki i zaawansowane techniki

Gdy poczujesz się bardziej komfortowo z funkcyjnymi metodami tablicowymi, weź pod uwagę te praktyki:

Przykład: Funkcyjne podejście do agregacji danych

Wyobraź sobie, że masz dane dotyczące sprzedaży z różnych regionów i chcesz obliczyć całkowitą sprzedaż dla każdego regionu, a następnie znaleźć region z najwyższą sprzedażą.

const salesData = [
  { region: 'North', amount: 100 },
  { region: 'South', amount: 150 },
  { region: 'North', amount: 120 },
  { region: 'East', amount: 200 },
  { region: 'South', amount: 180 },
  { region: 'North', amount: 90 }
];

// 1. Oblicz całkowitą sprzedaż na region za pomocą reduce
const salesByRegion = salesData.reduce((acc, sale) => {
  acc[sale.region] = (acc[sale.region] || 0) + sale.amount;
  return acc;
}, {});

// salesByRegion będzie: { North: 310, South: 330, East: 200 }

// 2. Przekształć zagregowany obiekt w tablicę obiektów do dalszego przetwarzania
const salesArray = Object.keys(salesByRegion).map(region => ({
  region: region,
  totalAmount: salesByRegion[region]
}));

// salesArray będzie: [
//   { region: 'North', totalAmount: 310 },
//   { region: 'South', totalAmount: 330 },
//   { region: 'East', totalAmount: 200 }
// ]

// 3. Znajdź region z najwyższą sprzedażą za pomocą reduce
const highestSalesRegion = salesArray.reduce((max, current) => {
  return current.totalAmount > max.totalAmount ? current : max;
}, { region: '', totalAmount: -Infinity }); // Zainicjuj bardzo małą liczbą

console.log('Sprzedaż według regionu:', salesByRegion);
console.log('Tablica sprzedaży:', salesArray);
console.log('Region z najwyższą sprzedażą:', highestSalesRegion);

/*
Wyjście:
Sprzedaż według regionu: { North: 310, South: 330, East: 200 }
Tablica sprzedaży: [
  { region: 'North', totalAmount: 310 },
  { region: 'South', totalAmount: 330 },
  { region: 'East', totalAmount: 200 }
]
Region z najwyższą sprzedażą: { region: 'South', totalAmount: 330 }
*/

Wniosek

Programowanie funkcyjne z tablicami JavaScript to nie tylko wybór stylistyczny; to potężny sposób pisania czystszego, bardziej przewidywalnego i bardziej solidnego kodu. Ujmując metody takie jak map, filter i reduce, możesz skutecznie przekształcać, badać i agregować swoje dane, przestrzegając jednocześnie podstawowych zasad programowania funkcyjnego, w szczególności niezmienności i czystych funkcji.

W miarę kontynuowania podróży w rozwoju JavaScript, włączenie tych wzorców funkcyjnych do codziennego przepływu pracy niewątpliwie doprowadzi do bardziej niezawodnych i skalowalnych aplikacji. Zacznij od eksperymentowania z tymi metodami tablicowymi w swoich projektach, a wkrótce odkryjesz ich ogromną wartość.

Mistrzowskie programowanie funkcyjne z tablicami JavaScript | MLOG