Продвинутое использование метода reduce

Перевод статьи 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), а также о том, как они работают, вы можете узнать в этой статье.

Комментарии