Начало работы с методами JavaScript-массивов .map(), .filter() и .reduce()

в 9:30, , рубрики: javascript, Блог компании RUVDS.com, разработка, Разработка веб-сайтов

Когда я разбирался в том, как пользоваться методами JS-массивов .map(), .filter() и .reduce(), всё, что я читал, смотрел и слушал, казалось мне очень сложным. Эти концепции рассматривались как некие самостоятельные механизмы, ни к чему другому отношения не имеющие. Мне тяжело было ухватить их суть и их понять.

Начало работы с методами JavaScript-массивов .map(), .filter() и .reduce() - 1

Я слышал, что это — базовые вещи, понимание которых является чем-то вроде границы между «посвящёнными» и «непосвящёнными». Хотелось бы мне тогда, чтобы мне сказали о них правду. Она заключается в том, что эти три метода символизируют то, что причины, по которым перебирают некие итерируемые объекты, часто вписываются в одну из трёх функциональных категорий.

Просматривая код, который я писал раньше, я понял, что в 95% случаев, когда я перебирал элементы строк или массивов, я выполнял одно из следующих действий:

  • Применение к каждому значению некоей последовательности инструкций (аналог метода .map()).
  • Фильтрация значений, соответствующих заданному критерию (то же, что делает .filter()).
  • Сведение набора данных к единственному агрегированному значению (аналог .reduce()).

Это был момент истины. Именно тогда я понял суть этих методов и увидел их связь с тем, что мне уже давно известно.

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

А теперь, без лишних слов, давайте поговорим об этих методах, и, в частности, посмотрим на то, как использовать их вместо широко распространённых схем применения циклов.

Метод .map()

Метод .map() используется в том случае, если нужно сделать следующее:

  1. Надо выполнить над каждым элементом итерируемого объекта некую последовательность действий.
  2. Надо вернуть значение, которое, предположительно, было изменено.

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

const prices = [19.99, 4.95, 25, 3.50];
let new_prices = [];
for(let i=0; i < prices.length; i++) {
   new_prices.push(prices[i] * 1.06);
}

Вот как сделать то же самое с помощью .map():

const prices = [19.99, 4.95, 25, 3.50];
let new_prices = prices.map(price => price * 1.06);

Тут используются довольно-таки лаконичные синтаксические конструкции. Поэтому давайте разберём этот пример. Метод .map() принимает коллбэк. Это — функция, которая будет применяться к элементам массива. В данном случае это — стрелочная функция, которая объявлена прямо в круглых скобках, следующих за объявлением метода.

Имя параметра price — это то имя, которое будет использоваться при работе с элементами массива. Так как наша стрелочная функция имеет всего один параметр — мы можем обойтись без круглых скобок при её объявлении.

Выражение после стрелки (=>) — это тело коллбэка. Так как в теле функции имеется лишь одно выражение — мы можем обойтись без фигурных скобок и без ключевого слова return.

Если такая запись кажется вам непонятной — вот немного расширенный вариант этого примера:

const prices = [19.99, 4.95, 25, 3.50];
let new_prices = prices.map((price) => {
   return price * 1.06
});

Метод .filter()

Метод .filter() применяется в тех случаях, когда из итерируемого объекта нужно выбрать некие элементы. При использовании этого метода нужно помнить о том, что значения, соответствующие фильтру, включаются в итоговый результат, а не исключаются из него. То есть — всё, для чего функция, переданная .filter(), возвратит true, будет оставлено.

Рассмотрим пример, в котором нужно отобрать из массива целых чисел только нечётные элементы. Здесь мы воспользуемся оператором взятия остатка от деления и будем выяснять — имеется ли остаток от деления каждого элемента массива на 2. Если остаток равен 1 — это говорит нам о том, что соответствующее число является нечётным. Сначала взглянем на способ решения этой задачи с помощью обычного цикла:

const numbers = [1,2,3,4,5,6,7,8];
let odds = [];
for(let i=0; i < numbers.length; i++) {
   if(numbers[i] % 2 == 1) {
      odds.push(numbers[i]);
   }
}

Метод .filter(), как и .map(), принимает один коллбэк, которому будут поочерёдно передаваться элементы итерируемого объекта:

const numbers = [1,2,3,4,5,6,7,8];
let odds = numbers.filter(num => num % 2);

Тут работа организована так же, как и в примере с .map(). Стрелочная функция, передаваемая .filter(), использует лишь один параметр, поэтому мы обходимся без круглых скобок. Её тело содержит лишь одно выражение, поэтому его можно не заключать в фигурные скобки и допустимо обойтись без return.

Метод .reduce()

И вот мы, наконец, добрались до метода .reduce(). Он, полагаю, самый непонятный из трёх рассматриваемых сегодня методов. Имя этого метода намекает на то, что он используется для сведения нескольких значений к одному. Однако мне кажется, что легче размышлять о нём как о методе, который позволяет собирать некие значения из частей, а не как о методе, который позволяет что-то «сворачивать» или «редуцировать».

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

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

const donations = [5, 20, 100, 80, 75];
let total = 0;
for(let i=0; i < donations.length; i++) {
   total += donations[i];
}

В отличие от методов .map() и .filter(), метод .reduce() нуждается в коллбэке, принимающем два параметра. Это — аккумулятор и текущее значение. Аккумулятор — это первый параметр. Именно он модифицируется на каждой итерации и передаётся в следующую:

const donations = [5, 20, 100, 80, 75];
let total = donations.reduce((total,donation) => {
   return total + donation;
});

Методу .reduce() тоже можно передать второй аргумент. Это — то, что будет играть роль начального значения для аккумулятора. Предположим, мы хотим узнать общую сумму пожертвований за два дня, учитывая то, что вчера эта сумма составила $450, а сведения о сегодняшних пожертвованиях хранятся в массиве:

const donations = [5, 20, 100, 80, 75];
let total = donations.reduce((total,donation) => {
   return total + donation;
}, 450);

Итоги

Надеюсь, теперь вы разобрались с методами JS-массивов .map(), .filter() и .reduce(). Воспринимайте их как механизмы, улучшающие читабельность вашего кода. Они позволяют писать более компактные программы, чем те, которые получаются при использовании обычных циклов. Но самая главная их сильная сторона заключается в том, что они позволяют ясно выразить намерение, которое лежит в основе кода.

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

Уважаемые читатели! Пользуетесь ли вы методами JS-массивов .map(), .filter() и .reduce()?

Автор: ru_vds

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js