Українська

Відкрийте для себе можливості функціонального програмування з масивами JavaScript. Навчіться ефективно трансформувати, фільтрувати та зводити дані за допомогою вбудованих методів.

Опановуємо функціональне програмування з масивами JavaScript

У постійно мінливому ландшафті веб-розробки JavaScript продовжує залишатися наріжним каменем. У той час як об’єктно-орієнтовані та імперативні парадигми програмування вже давно домінують, функціональне програмування (ФП) набирає значної популярності. ФП наголошує на незмінності, чистих функціях і декларативному коді, що веде до більш надійних, зручних в обслуговуванні та передбачуваних додатків. Один із найпотужніших способів застосувати функціональне програмування в JavaScript — це використовувати його власні методи масивів.

Цей вичерпний посібник розповість про те, як ви можете використати потужність принципів функціонального програмування за допомогою масивів JavaScript. Ми дослідимо ключові концепції та продемонструємо, як застосовувати їх за допомогою таких методів, як map, filter і reduce, трансформуючи спосіб обробки маніпулювання даними.

Що таке функціональне програмування?

Перш ніж заглиблюватися в масиви JavaScript, давайте коротко визначимо функціональне програмування. По суті, ФП — це парадигма програмування, яка розглядає обчислення як оцінку математичних функцій і уникає зміни стану та змінних даних. Ключові принципи включають:

Прийняття цих принципів може призвести до коду, який легше зрозуміти, тестувати та налагоджувати, особливо в складних додатках. Методи масивів JavaScript ідеально підходять для реалізації цих концепцій.

Сила методів масивів JavaScript

Масиви JavaScript оснащені багатим набором вбудованих методів, які дозволяють складне маніпулювання даними без використання традиційних циклів (наприклад, for або while). Ці методи часто повертають нові масиви, сприяючи незмінності, і приймають функції зворотного виклику, забезпечуючи функціональний підхід.

Давайте розглянемо найфундаментальніші функціональні методи масивів:

1. Array.prototype.map()

Метод map() створює новий масив, заповнений результатами виклику наданої функції для кожного елемента у викликаючому масиві. Він ідеально підходить для перетворення кожного елемента масиву на щось нове.

Синтаксис:

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

Основні характеристики:

Приклад: Подвоєння кожного числа

Уявіть, що у вас є масив чисел, і ви хочете створити новий масив, де кожне число подвоєно.

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

// Використання map для перетворення
const doubledNumbers = numbers.map(number => number * 2);

console.log(numbers); // Вихід: [1, 2, 3, 4, 5] (оригінальний масив не змінюється)
console.log(doubledNumbers); // Вихід: [2, 4, 6, 8, 10]

Приклад: Витяг властивостей з об’єктів

Звичайним випадком використання є вилучення певних властивостей з масиву об’єктів. Припустимо, у нас є список користувачів і ми хочемо отримати лише їхні імена.

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

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

console.log(userNames); // Вихід: ['Alice', 'Bob', 'Charlie']

2. Array.prototype.filter()

Метод filter() створює новий масив з усіма елементами, які проходять перевірку, реалізовану наданою функцією. Він використовується для вибору елементів на основі умови.

Синтаксис:

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

Основні характеристики:

Приклад: Фільтрування парних чисел

Давайте відфільтруємо масив чисел, щоб залишити лише парні числа.

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

// Використання filter для вибору парних чисел
const evenNumbers = numbers.filter(number => number % 2 === 0);

console.log(numbers); // Вихід: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(evenNumbers); // Вихід: [2, 4, 6, 8, 10]

Приклад: Фільтрування активних користувачів

З нашого масиву користувачів давайте відфільтруємо користувачів, які позначені як активні.

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); 
/* Вихід:
[
  { id: 1, name: 'Alice', isActive: true },
  { id: 3, name: 'Charlie', isActive: true }
]
*/

3. Array.prototype.reduce()

Метод reduce() виконує надану користувачем функцію зворотного виклику «reducer» для кожного елемента масиву по порядку, передаючи значення, що повертається з обчислення, для попереднього елемента. Кінцевим результатом виконання reducer для всіх елементів масиву є одне значення.

Це, мабуть, найуніверсальніший з методів масиву і є наріжним каменем багатьох шаблонів функціонального програмування, дозволяючи вам «звести» масив до одного значення (наприклад, суми, добутку, кількості або навіть нового об’єкта чи масиву).

Синтаксис:

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

Основні характеристики:

Приклад: Підсумовування чисел

Давайте підсумуємо всі числа в нашому масиві.

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

// Використання reduce для підсумовування чисел
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // 0 - це initialValue

console.log(sum); // Вихід: 15

Пояснення:

Приклад: Групування об’єктів за властивістю

Ми можемо використовувати reduce, щоб перетворити масив об’єктів на об’єкт, де значення згруповані за певною властивістю. Давайте згрупуємо наших користувачів за їхнім статусом 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;
}, {}); // Порожній об’єкт {} є initialValue

console.log(groupedUsers);
/* Вихід:
{
  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 }
  ]
}
*/

Приклад: Підрахунок випадків

Давайте порахуємо частоту кожного фрукта в списку.

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); // Вихід: { apple: 3, banana: 2, orange: 1 }

4. Array.prototype.forEach()

Хоча forEach() не повертає новий масив і часто вважається більш імперативним, оскільки його основна мета полягає у виконанні функції для кожного елемента масиву, він все ще є фундаментальним методом, який відіграє роль у функціональних шаблонах, особливо коли побічні ефекти необхідні або коли ітерація відбувається без потреби у перетвореному виводі.

Синтаксис:

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

Основні характеристики:

Приклад: Ведення журналу кожного елемента

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

messages.forEach(message => console.log(message));
// Вихід:
// Hello
// Functional
// World

Примітка: Для перетворень і фільтрування map і filter є кращими через їхню незмінність і декларативну природу. Використовуйте forEach, коли вам конкретно потрібно виконати дію для кожного елемента без збору результатів у нову структуру.

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

Ці методи корисні для пошуку конкретних елементів у масиві.

Приклад: Пошук користувача

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); // Вихід: { id: 2, name: 'Bob' }
console.log(bobIndex); // Вихід: 1
console.log(nonExistentUser); // Вихід: undefined
console.log(nonExistentIndex); // Вихід: -1

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

Ці методи перевіряють, чи всі елементи в масиві проходять перевірку, реалізовану наданою функцією.

Приклад: Перевірка статусу користувача

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); // Вихід: true (тому що Bob неактивний)
console.log(allAreActive); // Вихід: false (тому що Bob неактивний)

const allUsersActive = users.filter(user => user.isActive).length === users.length;
console.log(allUsersActive); // Вихід: false

// Альтернатива, використовуючи every безпосередньо
const allUsersActiveDirect = users.every(user => user.isActive);
console.log(allUsersActiveDirect); // Вихід: false

Зв’язування методів масивів для складних операцій

Справжня сила функціонального програмування з масивами JavaScript проявляється, коли ви зв’язуєте ці методи разом. Оскільки більшість із цих методів повертають нові масиви (за винятком forEach), ви можете плавно передавати вихід одного методу у вхід іншого, створюючи елегантні та читабельні конвеєри даних.

Приклад: Пошук імен активних користувачів і подвоєння їхніх ідентифікаторів

Давайте знайдемо всіх активних користувачів, витягнемо їхні імена, а потім створимо новий масив, де кожному імені передує число, що представляє його індекс у *відфільтрованому* списку, а їхні ідентифікатори подвоюються.

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) // Отримати лише активних користувачів
  .map((user, index) => ({      // Перетворити кожного активного користувача
    name: `${index + 1}. ${user.name}`,
    doubledId: user.id * 2
  }));

console.log(processedActiveUsers);
/* Вихід:
[
  { name: '1. Alice', doubledId: 2 },
  { name: '2. Charlie', doubledId: 6 },
  { name: '3. David', doubledId: 8 }
]
*/

Цей зв’язаний підхід є декларативним: ми вказуємо кроки (фільтрування, потім відображення) без явного керування циклом. Він також є незмінним, оскільки кожен крок створює новий масив або об’єкт, залишаючи оригінальний масив users недоторканим.

Незмінність на практиці

Функціональне програмування значною мірою покладається на незмінність. Це означає, що замість зміни існуючих структур даних ви створюєте нові з потрібними змінами. Методи масивів JavaScript, такі як map, filter і slice, за своєю суттю підтримують це, повертаючи нові масиви.

Чому важлива незмінність?

Коли вам потрібно виконати операцію, яка традиційно змінює масив (наприклад, додавання або видалення елемента), ви можете досягти незмінності за допомогою таких методів, як slice, синтаксис розгортання (...) або поєднання інших функціональних методів.

Приклад: Додавання елемента незмінно

const originalArray = [1, 2, 3];

// Імперативний спосіб (змінює originalArray)
// originalArray.push(4);

// Функціональний спосіб за допомогою синтаксису розгортання
const newArrayWithPush = [...originalArray, 4];
console.log(originalArray); // Вихід: [1, 2, 3]
console.log(newArrayWithPush); // Вихід: [1, 2, 3, 4]

// Функціональний спосіб за допомогою slice і конкатенації (менш поширений зараз)
const newArrayWithSlice = originalArray.slice(0, originalArray.length).concat(4);
console.log(newArrayWithSlice); // Вихід: [1, 2, 3, 4]

Приклад: Видалення елемента незмінно

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

// Видалити елемент за індексом 2 (значення 3)

// Функціональний спосіб за допомогою slice і синтаксису розгортання
const newArrayAfterSplice = [
  ...originalArray.slice(0, 2),
  ...originalArray.slice(3)
];
console.log(originalArray); // Вихід: [1, 2, 3, 4, 5]
console.log(newArrayAfterSplice); // Вихід: [1, 2, 4, 5]

// Використання filter для видалення певного значення
const newValueToRemove = 3;
const arrayWithoutValue = originalArray.filter(item => item !== newValueToRemove);
console.log(arrayWithoutValue); // Вихід: [1, 2, 4, 5]

Найкращі практики та передові методи

Оскільки ви стаєте більш комфортним з функціональними методами масивів, розгляньте ці практики:

Приклад: Функціональний підхід до агрегування даних

Уявіть, що у вас є дані про продажі з різних регіонів і ви хочете обчислити загальний обсяг продажів для кожного регіону, а потім знайти регіон з найвищими продажами.

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. Обчисліть загальний обсяг продажів за регіоном за допомогою reduce
const salesByRegion = salesData.reduce((acc, sale) => {
  acc[sale.region] = (acc[sale.region] || 0) + sale.amount;
  return acc;
}, {});

// salesByRegion буде: { North: 310, South: 330, East: 200 }

// 2. Перетворіть агрегований об’єкт на масив об’єктів для подальшої обробки
const salesArray = Object.keys(salesByRegion).map(region => ({
  region: region,
  totalAmount: salesByRegion[region]
}));

// salesArray буде: [
//   { region: 'North', totalAmount: 310 },
//   { region: 'South', totalAmount: 330 },
//   { region: 'East', totalAmount: 200 }
// ]

// 3. Знайдіть регіон з найвищими продажами за допомогою reduce
const highestSalesRegion = salesArray.reduce((max, current) => {
  return current.totalAmount > max.totalAmount ? current : max;
}, { region: '', totalAmount: -Infinity }); // Ініціалізуйте дуже малим числом

console.log('Sales by Region:', salesByRegion);
console.log('Sales Array:', salesArray);
console.log('Region with Highest Sales:', highestSalesRegion);

/*
Вихід:
Sales by Region: { North: 310, South: 330, East: 200 }
Sales Array: [
  { region: 'North', totalAmount: 310 },
  { region: 'South', totalAmount: 330 },
  { region: 'East', totalAmount: 200 }
]
Region with Highest Sales: { region: 'South', totalAmount: 330 }
*/

Висновок

Функціональне програмування з масивами JavaScript — це не просто стилістичний вибір; це потужний спосіб писати чистіший, передбачуваніший і надійніший код. Застосовуючи такі методи, як map, filter і reduce, ви можете ефективно перетворювати, запитувати та агрегувати свої дані, дотримуючись основних принципів функціонального програмування, особливо незмінності та чистих функцій.

Продовжуючи свій шлях у розробці JavaScript, інтеграція цих функціональних шаблонів у ваш щоденний робочий процес, безсумнівно, призведе до більш зручних в обслуговуванні та масштабованих програм. Почніть з експериментів з цими методами масивів у своїх проектах, і ви незабаром відкриєте їхню величезну цінність.