Понимание Javascript .reduce () и вариантов его использования

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

Во-первых, что это такое?

Причина, по которой я никогда не мог осмыслить этот метод, заключалась в том, что я не понимал слова «сокращение», определение Оксфорда таково:

Сделать меньше или меньше по количеству, степени или размеру.

Именно это и делает функция уменьшения. Он выполняет итерацию по массиву (группе элементов одного типа - в идеале) слева направо и уменьшает его до одного значения.

  • Примечание: единственным значением может быть объект или любой примитивный тип.

Самое простое объяснение

Представьте себе массив чисел:

Если бы мы хотели сложить все числа в массиве, мы могли бы использовать цикл forEach:

Метод forEach выполняет итерацию слева направо, начиная с 101.2 и прибавляя его к 0; затем более 242,2, добавив его к 101,2 и т. д.

Эквивалент метода reduce, в котором не используется беспорядочная предопределенная переменная, выходящая за пределы let total = 0, будет:

Здесь метод уменьшения применяется к массиву numbersToSum на каждой итерации - мы добавляем currentNumber, который зацикливается, к currentTotal (текущее общее значение по умолчанию равно 0 в начальной итерации - как бы, объяснено позже ).

Это происходит для каждого элемента в массиве numbersToSum, пока наша сумма не станет суммой всех записей в массиве numbersToSum .

Как это работает

Метод сокращения принимает 2 параметра .

Первый - функция обратного вызова, которая принимает до четырех параметров (все необязательные), а именно:

  1. previousValue: все, что вернула предыдущая итерация
  2. currentValue: текущее значение в массиве, по которому выполняется итерация.
  3. currentIndex: индекс текущего значения
  4. array: Итерация массива

Поскольку метод выполняет итерацию по элементам массива, он позволяет вам управлять previousValue, которое будет получено на следующей итерации. Другими словами, если мы запустим:

[1, 2, 3].reduce((previousValue, currentValue) => previousValue + currentValue) 

Для пояснений: в первой итерации мы возвращаем 0 + 1, во второй итерации наш previousValue теперь равен 1, и мы добавляем его к 2, чтобы изменить следующий previousValue. В нашей последней итерации наш previousValue равен 3, мы добавляем его к нашему currentValue из 3, и метод уменьшения возвращает значение 6, ожидаемое общее количество.

Второй параметр, следующий за обратным вызовом с 4 параметрами, - это initialValue, все, что он делает, это устанавливает previousValue, используемый в первой итерации.

Умная актуальность:

Если вы работали с функцией reduce несколько раз, то знаете, что функция reduce выполняет итерацию только дважды по массиву [1, 2, 3]. Это происходит по двум разумным причинам:

Во-первых, сокращается количество итераций. Если он принимает 1 как initialValue, это то же самое, что и первая итерация, возвращающая 0 + 1, поэтому мы можем правильно пропустить первую, 0 + 1, итерацию. Тогда первая итерация будет смотреть на 2 и добавлять к нему initialValue из 1.

Второй печатает; поскольку функция reduce может возвращать одно значение любого типа, при условии, что тип элемента, с которым она работает, с использованием первой записи массива упрощает ее жизнь. На первой итерации, если мы добавляем элемент к некоторому начальному значению, если значения не одного типа, это может стать беспорядочным - см. Ниже:

0 + {} !== {} + 0
0 + {} // returns "0[object Object]"
{} + 0 // returns 0

Если бы вместо этого мы инициализировали сокращение с помощью объекта, принудительно выполняя 3 итерации:

[1, 2, 3].reduce((total, number) => total + number, {})
// it would return "[object Object]123"
// yuck

Когда это используется?

Если бы вы сократили количество вариантов использования функции `reduce`, это было бы так:

Единственный вариант использования функции `reduce` - создавать кластеры данных.

Примеры использования

Один кластер: сумма чисел в массиве

[1, 2, 3].reduce((total, number) => total + number)
// returns 6

Один кластер: усреднение чисел в массиве

[1, 2, 3].reduce((total, number, index, array) => {
  total += number // add current number to total

  if (index === array.length - 1) { // if we're at the last iter.
    return total/array.length // return the average
  }
  return total
})
// returns 2 (= 6 / 3)

Одиночный кластер: плоский массив

Допустим, у вас есть массив статей в блоге, каждая из которых содержит массив комментариев. Если вы хотите получить список всех комментариев, вы можете использовать функцию уменьшения:

Множественные кластеры: количество предметов

Допустим, у нас есть массив строк, описывающих фрукты:

Если бы мы хотели вернуть объект, который дал бы нам подсчет каждого фрукта в массиве (не зная, какой фрукт мог бы быть в массиве - следовательно, мы не могли бы использовать .filter())

Мы могли бы просто написать:

const fruitTally = fruit.reduce((currentTally, currentFruit) => {
  currentTally[currentFruit] = (currentTally[currentFruit] || 0) + 1 
  return currentTally
} , {})
// returns {"apple":3,"banana":3,"cherry":2,"mango":2,"apricot":1,"guava":2}

Множественные кластеры: категоризация дат

Это, пожалуй, мой любимый вариант использования. Допустим, у вас есть список транзакций пользователей с отметками времени Unix, и вы хотите сгруппировать их по месяцам (чтобы пользователь мог открыть один месяц и закрыть остальные для лучшего UX).

В настоящее время данные слишком плоские, чтобы их можно было представить в удобном для пользователя виде, это может быть массив из 1000 записей. Мы можем аккуратно классифицировать это с помощью редуктора:

Заключение

Функция .reduce() позволяет нам делать довольно аккуратные манипуляции с данными. Он действительно эффективен, когда дело доходит до упрощения работы с данными или упрощения их представления там, где они должны быть прочитаны людьми.