Map, Filter и Reduce в JavaScript

Функциональное программирование в наши дни произвело настоящий фурор в мире разработки. И на то есть веская причина: функциональные методы могут помочь вам написать более декларативный код, который легче понять с первого взгляда, провести рефакторинг и тестирование.

Одним из краеугольных камней функционального программирования является его особое использование массивов и операций с ними. Эти вещи - именно то, на что они похожи: множество вещей и то, что вы с ними делаете. Но функциональное мышление относится к ним немного иначе, чем вы могли бы ожидать.

В этой статье мы подробно рассмотрим то, что привычно называть "большой тройкой" операций с массивами: Map, Filter и Reduce. Знакомство с этими тремя функциями является важным шагом на пути к умению писать чистый, функциональный код, и это открывает двери для чрезвычайно мощных методов функционального и реактивного программирования. Любопытно? Давайте погрузимся в это дело.

Map

Метод map используется для итерации по массиву. В каждой итерации он применяет функцию обратного вызова к текущему элементу массива и в конечном итоге возвращает совершенно новый массив.

В отличие от фильтра, map не отбрасывает элементы, а манипулирует значением элементов. Поэтому вы не можете пропустить элемент, если хотите. Новый массив будет иметь ту же длину , что и текущий.

Синтаксис

Arrays.map((element, index, array) => { ... })

Так же, как и фильтр, мы можем иметь три аргумента в map. Обычно нам нужно значение элемента.

Давайте рассмотрим простой для понимания пример. Предположим, вы хотите преобразовать список значений биткоинов в значения долларов.

Итак, один из способов — использовать for цикл. Начинаем с нулевого индекса до длины массива. В каждом индексе присваиваем преобразованное значение новому массиву в той же позиции. 👇🏻

const bitcoinList = [1, 3, 5, 4, 2];
const dollarList = [];

const bitcoinValue = 62953.10; // It's not a constant check it later!! :)

for (let i=0; i<bitcoinList.length; i++) {
    dollarList[i] = bitcoinList[i]*bitcoinValue;
}

console.log(dollarList); // [62953.1, 188859.3, 314765.5, 251812.4, 125906.2]

Но именно это и делает map.

Теперь посмотрите на код ниже, написанный с использованием map.

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

const bitcoinValue = 62,858.20; // It changed :(

const dollarList = bitcoinList.map((bitcoin) => bitcoin * bitcoinValue);

console.log(dollarList); // [62953.1, 188859.3, 314765.5, 251812.4, 125906.2]

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

Итак, здесь мы сопоставляем значения одного массива с другим, поэтому этот метод называется map.

Вот что вам нужно знать о методе map.

Filter

Как следует из названия, метод фильтрации используется для фильтрации элементов массива на основе заданного условия.

Элементы, для которых условие истинно, будут отфильтрованы и добавлены в новый массив, все остальные будут отброшены. Наконец, метод фильтра вернет совершенно новый массив.

Синтаксис

Array.filter((element, index, array) => { ... } )

У нас может быть три аргумента в фильтре: текущий elementи indexсам array. Функция обратного вызова будет иметь наше условие для фильтрации элементов (Вы можете сделать условие настолько сложным, насколько захотите).

Давайте рассмотрим пример, чтобы понять метод фильтрации.

Предположим, вы вызываете API, который возвращает список пользователей, имеющих некоторые данные. Вы хотите отфильтровать этот список на основе возраста отдельных пользователей.

Давайте напишем код:

const users = [
  {
    name: "Van Batchelder",
    city: "London",
    birthYear: 1998
  },
  {
    name: "Winter Rubino",
    city: "Madrid",
    birthYear: 1992
  },
  {
    name: "Yusuf Shea",
    city: "Paris",
    birthYear: 1990
  },
  {
    name: "Zion Shively",
    city: "Alabama",
    birthYear: 2002,
  }
];

const currentYear = new Date().getFullYear();
const filteredUsers = users.filter((user) => (currentYear - user.birthYear) > 25);

console.log(filteredUsers);
// [
//  {name: 'Winter Rubino', city: 'Madrid', birthYear: 1992},
//  {name: 'Yusuf Shea', city: 'Paris', birthYear: 1990}
// ]

Вы могли заметить, что функция обратного вызова возвращает логическое значение trueили false. На основе этого возвращаемого значения элемент добавляется или удаляется в новый массив.

Вот что вам нужно знать о методе фильтрации.

Reduce

Это наименее используемый метод массива (но поверьте, важный!) по сравнению с фильтром и map. Возможно, причина в том, что этот метод трудно понять (Но не после этой статьи 😉).

Как следует из названия, reduce используется для сокращения массива

Синтаксис

reduce((previous, current, index, array) => { ... }, initialValue)

Не беспокойтесь, глядя на синтаксис. После примера вам станет все ясно.

Примеры Reduce в интернете настолько просты, что мы не можем связать их с реальной проблемой. Но здесь мы поделемся реальным сценарием, с которым мы столкнулись и который подтолкнул нас к использованию Reduce.

Давайте возьмем тот же массив пользователей, который мы использовали в методе фильтра. Задача в том, что нам нужен список имен пользователей, имеющих возраст больше 25.

В этом массиве у вас могут быть пользователи с birthYear как NULL или undefined. Поэтому здесь вам нужно использовать метод фильтра, чтобы удалить пользователей с недействительным birthYear.

Из отфильтрованного массива вам нужны только имена пользователей, поэтому здесь вы будете использовать метод map для извлечения имен пользователей из объекта пользователя.

Видите ли, вам нужно использовать два метода для выполнения этой задачи. Но что, если мы скажем вам, что вы можете достичь результата, используя только один метод, и вы знаете, о каком методе мы говорим.

Давайте писать код.

const users = [
  {
    name: "Van Batchelder",
    city: "London",
    birthYear: 1998
  },
  {
    name: "Winter Rubino",
    city: "Madrid",
    birthYear: null
  },
  {
    name: "Yusuf Shea",
    city: "Paris",
    birthYear: 1990
  },
  {
    name: "Zion Shively",
    city: "Alabama",
    birthYear: 2002,
  }
];

const currentYear = new Date().getFullYear();

const userNames = users.reduce((filterUsers, user) => {
  if (user.birthYear && (currentYear - user.birthYear) > 25) {
    filterUsers.push(user.name);
  }
  return filterUsers;
}, []);

console.log(userNames);
// ['Yusuf Shea']

Давайте разберем код на основе синтаксиса. Здесь это filterUsers наш previous, user это наш текущий и пустой массив ([]) это наш initialValue синтаксис from. Нам не нужны indexand array в функции обратного вызова.

В первой итерации filterUsersбудет пустой массив на основе заданного initialValue. В теле функции мы ставим условие, что если у пользователя есть birthYear и его/ее возраст больше 25, то push его в filterUsersпротивном случае return filterUsers. Наконец, у нас userNamesесть отфильтрованный и сопоставленный список. 🤩

Вы можете обнаружить, что эту задачу можно решить, используя другие подходы, но поверьте мне, это лучший пример, который я могу вам привести для объяснения reduce.

Заключение и следующие шаги

Если подытожить:

filter: Когда вам нужно удалить нежелательный элемент из массива.

map: Преобразовать один массив в другой.

reduce: Когда вам нужно сократить массив.

В этом руководстве вы узнали, как отображать, фильтровать и сокращать работу; как их использовать; и примерно как они реализованы. Вы видели, что все они позволяют вам избежать изменения состояния, чего требует использование циклов for и forEach, и теперь у вас должно быть хорошее представление о том, как связать их все вместе.