Понимание 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 параметра .
Первый - функция обратного вызова, которая принимает до четырех параметров (все необязательные), а именно:
previousValue
: все, что вернула предыдущая итерацияcurrentValue
: текущее значение в массиве, по которому выполняется итерация.currentIndex
: индекс текущего значения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()
позволяет нам делать довольно аккуратные манипуляции с данными. Он действительно эффективен, когда дело доходит до упрощения работы с данными или упрощения их представления там, где они должны быть прочитаны людьми.