Уроки по веб-разработке

reduce() JS — как работает и когда его нужно применять

Данный метод наверное наиболее сложный из методов массивов. Давайте рассмотрим как с ним работать и разберем несколько примеров где он может применяться.

Метод reduce() выполняет заданную функцию к каждому элементу массива и возвращает «аккумулированный» результат.

array.reduce(function(total, currentValue, currentIndex, arr))

total — начальное значение или ранее возвращенное значение (из предыдущей итерации).
currentValue — текущее значение массива
currentIndex — индекс текущего элемента
arr — проходимый массив

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

Наиболее наглядный пример — это подсчет суммы значений элементов массива. Например, у нас есть такой массив:

const salary = [1000, 1200, 900, 1800];

const sum = salary.reduce((total, currentItem) => {
    total += currentItem;
    return total;
}, 0) // 0 - это начальное значение, тут мы могли бы указать другое число

console.log('sum', sum); // 4900

Рассмотрим другой пример. У нас есть массив пользователей. Каждый пользователь это обьект. У каждого пользователя есть имя, возраст и размер компенсации. Нам нужно высчитать среднюю заработную плату:

const users = [
    { name: 'John', age: 32, salary: 1500 },
    { name: 'Mike', age: 25, salary: 1200 },
    { name: 'Pieter', age: 27, salary: 1400 },
    { name: 'Jim', age: 22, salary: 1800 }
];

const averageSalary = users.reduce((total, user) => {
    total += user.salary/users.length;
    return total;
}, 0);

console.log('Average Salary', averageSalary); // 1475

Если в примере выше у вас users это не массив, а объект и задача та же — посчитать среднюю з/п, тогда мы можем сделать так:

const users = {
    0: { name: 'John', age: 32, salary: 1500 },
    1: { name: 'Mike', age: 25, salary: 1200 },
    2: { name: 'Pieter', age: 27, salary: 1400 },
    3: { name: 'Jim', age: 22, salary: 1800 }
}

// Определяем количество элементов в объекте
const userObjLength = Object.keys(users).length;

const averageSalary = Object.keys(users).reduce((total, key) => {
    // В [key] у нас попадает ключ с обьекта users (от 0 до 3)
    total += users[key].salary/userObjLength;
    return total;
}, 0);

console.log('Average Salary', averageSalary); // 1475

Преобразование массива в объект с помощью reduce

В двух последних примерах у нас есть массив users и есть объект users. А что если нам нужно преобразовать массив в объект? Для этого мы также можем воспользоваться методом reduce()

const users = [
    { name: 'John', age: 32, salary: 1500 },
    { name: 'Mike', age: 25, salary: 1200 },
    { name: 'Pieter', age: 27, salary: 1400 },
    { name: 'Jim', age: 22, salary: 1800 }
]

const usersObj = users.reduce((acc, user, index) => {
    acc[index] = user
    return acc;
    // Начальное значение {}
}, {}); 

console.log('New Object', usersObj);

Так как на выходе нам нужен объект, мы вторым параметром в метод reduce передаем пустой объект и дальше на каждой итерации добавляем ему новую пару «ключ — значение». В качестве ключа у нас выступает индекс текущего элемента в массиве.

Пишем свой метод reduce на JavaScript

Мы разберем два примера: простой и более сложный, в котором будут обрабатываться ошибки и проверяться условия.

Первый пример:

Array.prototype.customReduce = function(callback, result) {
    let i = 0;
    
    // Проверяем если кол-во аргументов меньше 2, значит не передано дефолтное значение
    if (arguments.length < 2) {
        i = 1;
        // В данном случае this указывает на итерируемый массив
        result = this[0]
    }

    for (; i < this.length; i++) {
        result = callback(result, this[i], i, this);
    }

    return result;
}

Мы сразу добавили данный метод к прототипу массива, чтобы его можно было легко вызвать. Теперь проверим выдаст ли он нам такой же результат, как был в самом первом примере этого поста:

const salary = [1000, 1200, 900, 1800];

const sum = salary.customReduce((total, currentItem) => {
    total += currentItem;
    return total;
}, 0);

console.log('sum', sum); // 4900

Теперь давайте рассмотрим более сложный вариант с дополнительными проверками:

Array.prototype.customReduce = function(callBack, initialValue) {
    // Проверяем не пустой ли массив и задано ли дефолтное значение
    if (!this.length && initialValue === undefined)
        throw new TypeError('Массив пустой. Невозможно к нему применить данный метод');

    // Если дефолтное значение задано, то присваиваем его к accumulator, который в итоге будем возвращать
    let accumulator = initialValue;
    let index = 0;

    if (initialValue === undefined) {
        // Если нет дефолтного значения, тогда accumulator будет присваиваться значение первого элемента массива
        accumulator = this[0];
        index = 1;
    }
    
    // В цикле проходимся по нашему массиву и передаем наш callback, который нужно выполнить для каждого элемента массива
    for (; index < this.length; index++) {
        // Как мы помним метод reduce принимает 4 параметра (см начало поста)
        // Поэтому мы в наш callback передаем такие же 4 параметра
        accumulator = callBack(accumulator, this[index], index, this);
    }

    return accumulator;
}

Если остались вопросы — пишите в комментарии.