Хабрахабр

[Из песочницы] Пять интересных способов использования Array.reduce() (и один скучный путь)

Представляю вашему вниманию перевод статьи "Five Interesting Ways to Use Array.reduce() (And One Boring Way)" автора Chris Ferdinandi. Привет, Хабр!

Из всех современных методов работы с массивами самым сложным из всех, что мне пришлось использовать, был Array.reduce().

Но, не смотря на свой скромный вид, Array.reduce() является мощным и гибким дополнением к вашему набору инструментов разработчика. На первый взгляд он кажется простым, скучным методом, который мало что дает.

Сегодня рассмотрим некоторые интересные вещи, которые можно сделать с помощью Array.reduce().

Как работает Array.reduce()

Метод Array.reduce() немного более гибкий. Большинство современных методов массива возвращают новый массив. Его цель — взять массив и сжать его содержимое в одно значение. Он может вернуть все что угодно.

Это та часть, которая всегда сбивала меня с толку — я не понимал, насколько она гибкая! Это значение может быть числом, строкой или даже объектом или новым массивом.

Синтаксис

Array.reduce() принимает два аргумента: метод callback, выполняемый для запуска каждого элемента в массиве, и начальное значение initialValue.

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

var myNewArray = [].reduce(function (accumulator, current) {
return accumulator;}, starting);
}, starting);

Рассмотрим несколько примеров

var myNewArray = [].reduce(function (accumulator, current) , starting);

1.Суммирование чисел

Используя Array.forEach(), можем сделать что-то вроде этого: Допустим, у есть массив чисел, которые хотим сложить вместе.

var total = 0;
[1, 2, 3].forEach(function (num) { total += num;
});

Слово «accumulator» сбивает с толку, поэтому в этом примере назовем его «sum», потому что это то, что оно есть по своей сути. Это пример-клише для использования Array.reduce().

var total = [1, 2, 3].reduce(function (sum, current) { return sum + current;
}, 0);
Здесь мы передаем 0 как наше начальное значение.

В обратном вызове мы добавляем текущее значение к сумме, которая имеет начальное значение 0 в первом цикле, затем 1 (начальное значение 0 плюс значение элемента 1), затем 3 (суммарное значение 1 плюс значение элемента 2) и так далее.
Пример.

2.Альтернатива комбинированию методов массива Array.map() и Array.filter() в одном шаге

Представим, что в Хогвартсе множество волшебников.

var wizards = [ { name: 'Harry Potter', house: 'Gryfindor' }, { name: 'Cedric Diggory', house: 'Hufflepuff' }, { name: 'Tonks', house: 'Hufflepuff' }, { name: 'Ronald Weasley', house: 'Gryfindor' }, { name: 'Hermione Granger', house: 'Gryfindor' }];

Один из способов сделать это — использовать метод Array.filter(), чтобы получить обратно только тех волшебников, у которых свойство дома — Хаффлпафф. Хотим создать новый массив, который будет содержать только имена мастеров из Хаффлпаффа. Затем используем метод Array.map() для создания нового массива, содержащего только свойство name для остальных мастеров.

// Получаем имена волшебников из Хаффлпафф
var hufflepuff = wizards.filter(function (wizard) { return wizard.house === 'Hufflepuff';
}).map(function (wizard) { return wizard.name;
});

Передаем пустой массив ([]) в качестве начального значения. С помощью метода Array.reduce() можно получить один и тот же массив за один проход, что улучшит нашу производительность. Если это так, отправляем его в newArr (наш accumulator в этом примере). На каждом проходе проверяем, является ли wizard.house Хаффлпаффом. Если нет, ничего не делаем.

В любом случае, возвращаем newArr, чтобы получить accumulator на следующем проходе.

// Получаем имена волшебников из Хаффлпафф
var hufflepuff = wizards.reduce(function (newArr, wizard) { if (wizard.house === 'Hufflepuff') { newArr.push(wizard.name); } return newArr;
}, []);

Пример.

3.Создание разметки из массива

Вместо пустого массив в Array.reduce() в качестве нашего начального значения, передадим пустую строку ('') и назовем ее html. Что если вместо создания массива имен, хотим создать неупорядоченный список мастеров в Хаффлпаффе?

Затем вернем HTML, как accumulator в следующем цикле. Если wizard.house равен Hufflepuff, мы объединяем нашу html-строку с wizard.name, обернутым в открывающий и закрывающий элементы списка (li).

// Создание списка волшебников из Хаффлпафф
var hufflepuffList = wizards.reduce(function (html, wizard) { if (wizard.house === 'Hufflepuff') { html += '<li>' + wizard.name + '</li>'; } return html;
}, '');

Теперь все готово для добавления разметки в DOM. Добавим открывающий и закрывающий неупорядоченный элемент списка до и после Array.reduce().

// Создание списка волшебников из Хаффлпафф
var hufflepuffList = '<ul>' + wizards.reduce(function (html, wizard) { if (wizard.house === 'Hufflepuff') { html += '<li>' + wizard.name + '</li>'; } return html;
}, '') + '</ul>';

Пример.

4.Группировка похожих элементов в массив

В библиотеке lodash есть метод groupBy(), который принимает коллекцию элементов в виде массива и группирует их в объект на основе некоторых критериев.

Допустим, нам нужен массив чисел.

Если хотим сгруппировать все элементы в числа по их целочисленному значению, то сделать это следует с помощью lodash.

var numbers = [6.1, 4.2, 6.3];
// returns {'4': [4.2], '6': [6.1, 6.3]}
_.groupBy(numbers, Math.floor);

Если имеется массив слов, и нужно сгруппировать элементы в словах по их длине, мы бы это сделали.

var words = ['one', 'two', 'three'];
// returns {'3': ['one', 'two'], '5': ['three']}
_.groupBy(words, 'length');

Создание функции groupBy() с помощью Array.reduce()

Можно воссоздать ту же функциональность, используя метод Array.reduce().

Внутри groupBy() мы будем запускать Array.reduce() для нашего массива, передавая пустой объект ({}) в качестве отправной точки и возвращая результат. Cоздадим вспомогательную функцию groupBy(), которая принимает массив и критерии для сортировки в качестве аргументов.

var groupBy = function (arr, criteria) { return arr.reduce(function (obj, item) { // Some code will go here... }, {});
};

Тогда мы получим его значение из текущего элемента. Внутри Array.reduce() функцией callback проверим, является ли критерий функцией, применяемой к элементу, или же свойством элемента.

Наконец, добавим элемент в это свойство и вернем объект в качестве accumulator для следующего цикла. Если в объекте пока нет свойства с этим значением, создадим его[свойство] и назначим пустой массив в качестве его значения.

var groupBy = function (arr, criteria) { return arr.reduce(function (obj, item) { // Проверка на то, является ли критерий функцией элемента или же //свойством элемента var key = typeof criteria === 'function' ? criteria(item) : item[criteria]; // Если свойство не создано, создаем его. if (!obj.hasOwnProperty(key)) { obj[key] = []; } // Добавление значения в объект
obj[key].push(item); // Возвращение объекта для следующего шага
return obj; }, {});};

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

Эту вспомогательную функцию и многое другое можно найти в Vanilla JS Toolkit. Отдельное спасибо Тому Бремеру за помощь.

5.Объединение данных из двух источников в массив

Вспомним наш список волшебников.

var wizards = [ { name: 'Harry Potter', house: 'Gryfindor' }, { name: 'Cedric Diggory', house: 'Hufflepuff' }, { name: 'Tonks', house: 'Hufflepuff' }, { name: 'Ronald Weasley', house: 'Gryfindor' }, { name: 'Hermione Granger', house: 'Gryfindor' }];

Что делать, если бы был другой набор данных — объект c домом и очками, которые заработал каждый маг.

var points = { HarryPotter: 500, CedricDiggory: 750, RonaldWeasley: 100, HermioneGranger: 1270
};

Как это сделать? Представим, что хотим объединить оба набора данных в один массив с количеством очков, добавленных к данным каждого волшебника в массиве wizards.

Метод Array.reduce() идеально подходит для этого!

var wizardsWithPoints = wizards.reduce(function (arr, wizard) { // Получаем значение для объекта points, удалив пробелы из имени //волшебника var key = wizard.name.replace(' ', ' '); // Если у волшебника есть очки, устанавливаем значение, // иначе устанавливаем 0. if (points[key]) { wizard.points = points[key]; } else { wizard.points = 0; } // Добавляем объект wizard в новый массив. arr.push(wizard); // Возвращаем массив. return arr;
}, []);

Пример объединения данных из двух источников в массив.

6.Объединение данных из двух источников в объект

Опять же, метод Array.reduce() идеально подходит для этого. Что, если вместо этого необходимо объединить два источника данных в объект, в котором имя каждого волшебника это ключ (key), а их дом и очки — свойства?

var wizardsAsAnObject = wizards.reduce(function (obj, wizard) { // Получаем значение ключа для объекта points, удалив пробелы из имени //волшебника var key = wizard.name.replace(' ', ' '); // Если у волшебника есть очки, устанавливаем значение, // иначе устанавливаем 0. if (points[key]) { wizard.points = points[key]; } else { wizard.points = 0; } // Удаляем свойство name delete wizard.name; // Добавляем значение wizard в новый объект obj[key] = wizard; // Возвращаем массив return obj;
}, {});

Пример объединения данных из двух источников в объект.

Стоит ли использовать Array.reduce()?

Итак, стоит ли его использовать? Метод Array.reduce() превратился из бессмысленного в мой любимый метод JavaScript. И когда же?

Работает как во всех современных браузерах так и в IE9. Метод Array.reduce() обладает фантастической поддержкой браузеров. Если нужно еще больше, то можно добавить полифилл, чтобы вернуть поддержку в IE6. Уже долгое время поддерживается мобильными браузерами.

Комбинация методов Array.filter() с Array.map() выполняется медленнее и включает дополнительные шаги, но ее легче читать. Самой серьезной проблемой может быть то, что Array.reduce() сбивает с толку людей, которые никогда не сталкивались с ним[методом] раньше. Из названий методов видно, что они должны делать.

Хорошим примером является вспомогательная функция groupBy(). Как уже было сказано, метод Array.reduce(), в целом, упрощает более сложные вещи.

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

Об авторе

Он считает, что есть более простой и надежный способ делать вещи для интернета. Крис Фердинанди помогает людям изучать ванильный JavaScript.

Его бюллетень советов разработчикам читают тысячи разработчиков каждый будний день. Крис является автором серии Vanilla JS Pocket Guide, создателем учебной программы Vanilla JS Academy и ведущим Vanilla JS Podcast.

Крис Койер, основатель CSS-Tricks и CodePen, описал его работу как "бесконечно цитируемую". Он обучал разработчиков в таких организациях, как Chobani и Boston Globe, а его плагины JavaScript были использованы Apple и Гарвардской школой бизнеса.

Он ведет Go Make Things с щенком Бейли, лабораторной помесью из Теннесси. Крис любит пиратов, щенков и фильмы Pixar, а также живет рядом с лошадиными фермами в сельской местности Массачусетса.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»