[Из песочницы] Пять интересных способов использования 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.
Callback также принимает два аргумента: accumulator, который является текущим объединенным значением, и текущий элемент в цикле currentValue. Все, что вы возвращаете, используется в качестве accumulator для следующего элемента в цикле. В самом первом цикле вместо этого используется начальное значение.
var myNewArray = [].reduce(function (accumulator, current) {
return accumulator;}, starting);
}, starting);
Рассмотрим несколько примеров
var myNewArray = [].reduce(function (accumulator, current) {
return accumulator;}, starting);
1.Суммирование чисел
Допустим, у есть массив чисел, которые хотим сложить вместе. Используя Array.forEach (), можем сделать что-то вроде этого:
var total = 0;
[1, 2, 3].forEach(function (num) {
total += num;
});
Это пример-клише для использования Array.reduce (). Слово «accumulator» сбивает с толку, поэтому в этом примере назовем его «sum», потому что это то, что оно есть по своей сути.
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 () можно получить один и тот же массив за один проход, что улучшит нашу производительность. Передаем пустой массив ([]) в качестве начального значения. На каждом проходе проверяем, является ли wizard.house Хаффлпаффом. Если это так, отправляем его в newArr (наш accumulator в этом примере). Если нет, ничего не делаем.
В любом случае, возвращаем newArr, чтобы получить accumulator на следующем проходе.
// Получаем имена волшебников из Хаффлпафф
var hufflepuff = wizards.reduce(function (newArr, wizard) {
if (wizard.house === 'Hufflepuff') {
newArr.push(wizard.name);
}
return newArr;
}, []);
Пример.
3.Создание разметки из массива
Что если вместо создания массива имен, хотим создать неупорядоченный список мастеров в Хаффлпаффе? Вместо пустого массив в Array.reduce () в качестве нашего начального значения, передадим пустую строку ('') и назовем ее html.
Если wizard.house равен Hufflepuff, мы объединяем нашу html-строку с wizard.name, обернутым в открывающий и закрывающий элементы списка (li). Затем вернем HTML, как accumulator в следующем цикле.
// Создание списка волшебников из Хаффлпафф
var hufflepuffList = wizards.reduce(function (html, wizard) {
if (wizard.house === 'Hufflepuff') {
html += '' + wizard.name + ' ';
}
return html;
}, '');
Добавим открывающий и закрывающий неупорядоченный элемент списка до и после Array.reduce (). Теперь все готово для добавления разметки в DOM.
// Создание списка волшебников из Хаффлпафф
var hufflepuffList = '' + wizards.reduce(function (html, wizard) {
if (wizard.house === 'Hufflepuff') {
html += '- ' + wizard.name + '
';
}
return html;
}, '') + '
';
Пример.
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 ().
Cоздадим вспомогательную функцию groupBy (), которая принимает массив и критерии для сортировки в качестве аргументов. Внутри groupBy () мы будем запускать Array.reduce () для нашего массива, передавая пустой объект ({}) в качестве отправной точки и возвращая результат.
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.Объединение данных из двух источников в объект
Что, если вместо этого необходимо объединить два источника данных в объект, в котором имя каждого волшебника это ключ (key), а их дом и очки — свойства? Опять же, метод Array.reduce () идеально подходит для этого.
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. Итак, стоит ли его использовать? И когда же?
Метод Array.reduce () обладает фантастической поддержкой браузеров. Работает как во всех современных браузерах так и в IE9. Уже долгое время поддерживается мобильными браузерами. Если нужно еще больше, то можно добавить полифилл, чтобы вернуть поддержку в IE6.
Самой серьезной проблемой может быть то, что Array.reduce () сбивает с толку людей, которые никогда не сталкивались с ним[методом] раньше. Комбинация методов Array.filter () с Array.map () выполняется медленнее и включает дополнительные шаги, но ее легче читать. Из названий методов видно, что они должны делать.
Как уже было сказано, метод Array.reduce (), в целом, упрощает более сложные вещи. Хорошим примером является вспомогательная функция groupBy ().
В конечном счете, это еще один инструмент для вашего инструментария. Инструмент, который, если его правильно использовать, может дать сверхспособности.
Об авторе
Крис Фердинанди помогает людям изучать ванильный JavaScript. Он считает, что есть более простой и надежный способ делать вещи для интернета.
Крис является автором серии Vanilla JS Pocket Guide, создателем учебной программы Vanilla JS Academy и ведущим Vanilla JS Podcast. Его бюллетень советов разработчикам читают тысячи разработчиков каждый будний день.
Он обучал разработчиков в таких организациях, как Chobani и Boston Globe, а его плагины JavaScript были использованы Apple и Гарвардской школой бизнеса. Крис Койер, основатель CSS-Tricks и CodePen, описал его работу как «бесконечно цитируемую».
Крис любит пиратов, щенков и фильмы Pixar, а также живет рядом с лошадиными фермами в сельской местности Массачусетса. Он ведет Go Make Things с щенком Бейли, лабораторной помесью из Теннесси.