JS. Просто о сложном: filter, map, reduce

Функции высшего порядка — элегантное решение, которое делает код проще, понятнее и эффективнее. Также при первом знакомстве они — верный источник головной боли, от которой не спасают ни гайды в интернете, ни попытки объяснить происходящее словами «свертка» и «отображение».
Этому весьма способствует сложившаяся традиция демонстрировать логику таких функций абстрактными примерами, в которых суммируются какие-то a и b:
Я предлагаю разобрать принцип действия filter, map и reduce на примере, приближенном к жизни и наконец-то разложить все по полочкам.
Жизненный пример
Представим, что, отчаявшись разобраться с функциями высшего порядка самостоятельно, вы создаете топик в блоге с приблизительно таким заголовком:
'Пробую использовать функции высшего порядка,
но ничего не выходит!!!!!'
Видя количество восклицательных знаков в вашем предложении, неравнодушные пользователи бросаются делиться ценными советами. Они пишут комментарии, и, с точки зрения сайта, на котором все происходит (ну ладно, конечно, это Хекслет), комментарии пользователей складываются в один большой массив:
Как видите, массив представлен объектами, каждый из которых описывает один комментарий: кто его написал, какой у этого пользователя рейтинг, что именно написано и так далее (конечно, в реальности все немного сложнее, но для демонстрации работы наших функций этого достаточно).
Чтобы эффективно манипулировать таким массивом и использовать данные внутри, нам как раз очень пригодятся filter
, map
и reduce
.
Содержание
I. Filter
Функция filter
самая простая и понятная из великолепной тройки. Она проходится по массиву и отбирает только те элементы, которые подходят под заданное условие. А те, которые не подходят, соответственно, игнорирует.
Допустим, мы хотим выбрать из нашей коллекции только те комментарии, в которых упоминается console.log: в конце концов, отладка — это самый важный инструмент для понимания происходящего в коде, наверняка такой совет поступит не единожды.
Применим filter
:
Что здесь произошло? В функцию filter
мы передали callback — по большому счету, просто функцию, аргументом которой является наш элемент коллекции — комментарий пользователя.
Для каждого элемента коллекции мы выбрали интересующую нас деталь, а именно, текст комментария comment.text
, и проверили, содержит ли текст подстроку «console.log». Если это так, callback вернет true
, и весь наш объект-комментарий будет добавлен в результат. В противном случае, callback вернет false
, и результат не изменится.
Вот, кстати, и он:
Мы успешно отфильтровали комментарии по условию, дело сделано!
Рекомендую вам самостоятельно потестировать этот код на repl.it, чтобы убедиться, что все работает именно так. Последуйте совету Ивана Редьюсова — используйте отладку и посмотрите, как добавляются элементы ;)
II. Map
Функция map немного сложнее. Для каждого обработанного элемента коллекции она добавит в результат один элемент, измененный так, как мы укажем в callback функции.
В этом примере мы даже не изменили, а заменили элементы. Функция вернула 5 на каждый элемент изначального массива. Практического смысла в этом немного, но сам механизм вы должны понимать — мы могли бы вставить вместо числа массив или объект, и функция map также заполнила бы результирующий массив указанными сущностями в соотношении 1
(один элемент изначального массива — один элемент конечного массива, вне зависимости от его внутренней сложности).Вернемся к нашему массиву комментариев. Допустим, мы хотим получить коллекцию имен всех пользователей, которые отписались в вашем топике:
Отображение сработало как надо, и мы получили интересующие нас детали. Попробуйте самостоятельно извлечь из комментариев другие элементы. Измените их прямо в callback функции — к примеру, извлеките рейтинг пользователей и переведите его в двоичную систему счисления!
Читайте также:
Что такое callback-функция в JavaScript?
III. Reduce
Функция reduce
, наверное, самая сложная из нашей тройки, ведь, помимо элементов коллекции, в ней появляется аккумулятор, с которым нужно научиться правильно работать. Эта функция производит «свертку», то есть, берет элементы из коллекции и из их множества создает какую-то одну новую сущность. Например, из массива — объект или число.
Технически функция reduce
может заменить и filter
, и map
, но это, скорее всего, введет в заблуждение ваших коллег-программистов, поэтому старайтесь применять каждую функцию по прямому назначению. Для фильтрации — фильтрацию, для свертки — свертку.
Теперь попробуем применить функцию reduce
. Предположим, мы хотим, имея нашу коллекцию комментариев, создать объект, в котором ключами будут имена пользователей, а значениями — все комментарии данного пользователя.
Давайте пошагово разберем, что здесь происходит.
Из примечательного — у нас появляется аккумулятор. Это переменная, в которую мы будем складывать промежуточные результаты. Вспомните — функции высшего порядка обрабатывают элемент за элементом, а на выходе из reduce
у нас должна получиться некая новая сущность. Ее мы будем наполнять последовательно, так что без аккумулятора не обойтись. В нашем случае переменная называется acc
, но вы, конечно, можете придумать любое другое имя.
Во-вторых, у нас появляется инициализатор типа, к которому мы сводим коллекцию. В нашем случае это объект, поэтому ставим {}
. На этом месте мог бы быть массив []
, строка ''
или число.
Теперь давайте разберемся с внутренней логикой callback функции.
В нашем примере возможны два сценария: в объекте уже есть ключ (имя пользователя), и тогда мы должны добавить в значения новый комментарий; или в объекте еще нет такого ключа, и тогда нам нужно его создать.
Что происходит здесь? Мы проверяем, есть ли в аккумуляторе искомый ключ. Для этого используем функцию _.has()
из библиотеки Lodash. Если ключ есть, то добавляем текст комментария в значение ключа (оно представлено массивом).
И возвращаем аккумулятор — это важно!
Если же ключа нет, тогда добавляем его в наш результирующий объект, и передаем ему значение — массив с одним элементом, то есть, первым найденным нами комментарием этого пользователя. А с помощью spread-оператора мы копируем в объект всю накопленную аккумулятором информацию. Не забываем про возврат.
И вот он, долгожданный результат:
Как видите, из массива со множеством элементов мы создали один-единственный объект, зато наполнили его нужным нам содержимым. Попробуйте применить другую логику, например, вместо имени, используйте рейтинг пользователя.
IV. Бонус — функция forEach и особый синтаксис
Среди функций высшего порядка есть еще один любопытный экземпляр, функция forEach. Она используется для перебора элементов массива прямо как цикл for...of. Но, в отличие от цикла, более гибко встраивается в синтаксис функций высшего порядка.
Особенность этого синтаксиса в том, что функции высшего порядка можно запускать последовательно, как методы, не создавая промежуточных констант:
Не стану подробно расписывать этот пример, думаю, теперь вы можете провернуть такую операцию самостоятельно!
Заключение
Спасибо, что прочитали эту статью! Надеюсь, функции высшего порядка стали вам немного понятнее и ближе. Практикуйтесь, и они станут вашими лучшими помощниками.
Желаю удачи в освоении JavaScript!
Андрей Агафонов
5 лет назад