Перевод статьи How to reduce() arrays от jstips.co с небольшими дополнениями.
Метод массивов reduce
не похож на другие перебирающие методы массивов: результатом его выполнения может быть значение любого типа данных, которое задаёте сами. Именно такая особенность может сделать reduce
чрезвычайно мощным инструментом в руках опытного разработчика.
Обычно reduce
используется для работы с числами и строками и позволяет проводить "аккумуляцию" значений. Пример использования уже успевший стать классическим:
var nums = [10, 20, 30, 40, 50];
var sum = nums.reduce(function(result, num) {
return result + num;
}, 0);
console.log(sum); // 150 сумма всех элементов массива
Reduce
принимает два аргумента: callback
функцию и начальное значение, которое будет присвоено аргументу result
в примере выше при первой итерации. callback
функция принимает целых 4 аргумента: промежуточное значение (аргумент result
в примере выше), элемент массива, индекс элемента и сам массив. После каждой итерации в промежуточное значение записываются новые данные, которые берутся из результата выполнения функции callback
при прошлой итерации:
var nums = [10, 20, 30, 40, 50];
var sum = nums.reduce(function(result, num) {
console.log(result);
return result + num;
}, 0);
// Будет выведено в консоль
// 0 начальное значение
// 10 начальное значение + первый элемент в массиве = промежуточное значение
// 30 промежуточное значение + второй элемент в массиве = промежуточное значение
// 60 и так далее
// 100
Разумеется, reduce
может работать с любыми типами данных, не только с числами. Пример со строками (в данном случае в качестве начального значения стоит передавать пустую строку):
// Пример реализации метода join(' ')
var strs = ['JavaScript', 'is', 'awesome'];
var result = strs.reduce(function(phrase, word, index) {
// Перед первым словом не надо ставить пробел
return (index === 0) ? phrase + word : phrase + ' ' + word;
}, '');
console.log(result); // JavaScript is awesome
Пример более продвинутого использования reduce
был подсмотрен у Redux.
Представьте, что у вас есть каталог товаров, у каждого из которых есть определённый набор свойств, в том числе и свойство price
, которое и возьмём для примера. Ваш покупатель выбирает товары и добавляет их в корзину. Таким образом, заходя в корзину, пользователь остаётся с выбранными им товарами, которые мы, как разработчики, будем считать массивом объектов подобного вида:
var selected = [
{ price: 20 },
{ price: 45 },
{ price: 67 },
{ price: 1305 }
];
Отлично! Уже сейчас мы можем вычислить общую стоимость товаров и выставить покупателю счёт. Или нет? Магазин у вас интернациональный и каждый клиент вправе выбрать ту валюту, с помощью которой ему будет удобно рассчитаться. Чтобы удобно всё это оформить создадим объект функций "редюсеров", которые будут вычислять стоимость, исходя из курса рубля:
var reducers = {
rubles: function(state, item) {
return state.rubles += item.price;
},
dollars: function(state, item) {
return state.dollars += item.price / 71.6024;
},
euros: function(state, item) {
return state.euros += item.price / 79.0133;
},
yens: function(state, item) {
return state.yens += item.price / 0.6341;
},
pounds: function(state, item) {
return state.pounds += item.price / 101.7829;
}
};
Получая массив с ценами товаров, мы хотим рассчитать общую цену для каждого типа валюты и на выходе получить объект вида:
var totalPrice = {
rubles: 1437,
dollars: 20.06,
euros: 18.18,
yens: 2266.20,
pounds: 14.15
};
Чтобы получить возможность автоматически использовать все запрашиваемые callback
функции из объекта редюсеров нужно написать ещё одну функцию-обёртку, которая будет вычислять все переданные ей значения цен, последовательно вызывая функцию-редюсер для каждого типа валют:
var combineReducers = function(reducers) {
return function(state, item) {
return Object.keys(reducers).reduce(function(nextState, key) {
reducers[key](state, item);
return state;
}, {});
}
};
И это всё, что нам было необходимо сделать. Теперь можем посмотреть, как это всё будет работать:
// Получаем функцию, которая будет всё обрабатывать
var priceReducer = combineReducers(reducers);
// и вычисляем общую стоимость, задавая объект с изначальными значениями
var totalPrice = selected.reduce(priceReducer, {
rubles: 0,
pounds: 0,
dollars: 0,
euros: 0,
yens: 0
});
console.log(totalPrice);
// {
// "rubles": 1437,
// "pounds": 14.118285095040523,
// "dollars": 20.069159692971187,
// "euros": 18.186811587416294,
// "yens": 2266.204068758871
// }
Подробнее о других методах работы с массивами (map, filter, forEach, some, every), а также о том, как они работают, вы можете узнать в этой статье.