Array.prototype.reduce()
Базовая поддержка Широко доступна
Эта функция хорошо зарекомендовала себя и работает на многих устройствах и версиях браузеров. Она доступна во всех браузерах с июля 2015 года.
Метод reduce() экземпляров Array выполняет пользовательскую функцию обратного вызова "reducer" для каждого элемента массива, по порядку, передавая ей результат предыдущего вычисления. Окончательный результат выполнения reducer по всем элементам массива — это одно значение.
При первом вызове callback не существует "результата предыдущего вычисления". Если предоставлено начальное значение, оно может быть использовано вместо него. В противном случае в качестве начального значения используется элемент массива с индексом 0, и итерация начинается со следующего элемента (индекс 1 вместо индекса 0).
Попробуйте
const array = [1, 2, 3, 4]; // 0 + 1 + 2 + 3 + 4 const initialValue = 0; const sumWithInitial = array.reduce( (accumulator, currentValue) => accumulator + currentValue, initialValue, ); console.log(sumWithInitial); // Expected output: 10
Синтаксис
reduce(callbackFn) reduce(callbackFn, initialValue)
Параметры
-
callbackFn - Функция, выполняемая для каждого элемента массива. Её возвращаемое значение становится значением параметра
accumulatorпри следующем вызовеcallbackFn. Для последнего вызова возвращаемое значение становится возвращаемым значениемreduce(). Функция вызывается со следующими аргументами:-
accumulator - Значение, полученное в результате предыдущего вызова
callbackFn. При первом вызове его значение равноinitialValue, если последнее указано; в противном случае его значение равноarray[0]. -
currentValue - Значение текущего элемента. При первом вызове его значение равно
array[0], если указанinitialValue; в противном случае его значение равноarray[1]. -
currentIndex - Позиция индекса
currentValueв массиве. При первом вызове его значение равно0, если указанinitialValue, иначе1. -
array - Массив, для которого был вызван
reduce().
-
-
initialValueНеобязательный - Значение, которому
accumulatorинициализируется при первом вызове callback. Если указанinitialValue,callbackFnначинает выполняться с первым значением в массиве в качествеcurrentValue. ЕслиinitialValue*не* указан,accumulatorинициализируется первым значением в массиве, аcallbackFnначинает выполняться со вторым значением в массиве в качествеcurrentValue. В этом случае, если массив пуст (так что нет первого значения для возврата в качествеaccumulator), выбрасывается ошибка.
Возвращаемое значение
Значение, полученное в результате полного выполнения функции reducer для всего массива.
Исключения
-
TypeError - Выбрасывается, если массив не содержит элементов, а
initialValueне предоставлено.
Описание
Метод reduce() является итеративным методом. Он выполняет функцию обратного вызова "reducer" для всех элементов массива, в порядке возрастания индексов, и накапливает их в одно значение. Каждый раз возвращаемое значение callbackFn передается в callbackFn при следующем вызове в качестве accumulator. Окончательное значение accumulator (которое является значением, возвращенным из callbackFn при последней итерации массива) становится возвращаемым значением reduce(). Прочтите раздел итеративные методы для получения дополнительной информации о том, как эти методы работают в целом.
callbackFn вызывается только для индексов массива, которым были присвоены значения. Он не вызывается для пустых слотов в разреженных массивах.
В отличие от других итеративных методов, reduce() не принимает аргумент thisArg. callbackFn всегда вызывается с undefined в качестве this, который подставляется globalThis, если callbackFn не в строгом режиме.
reduce() является центральным понятием в функциональном программировании, где невозможно изменить какое-либо значение, поэтому для накопления всех значений в массиве необходимо возвращать новое значение аккумулятора при каждой итерации. Это соглашение переносится на reduce() в JavaScript: следует использовать распространение или другие методы копирования, где это возможно, для создания новых массивов и объектов в качестве аккумулятора, вместо изменения существующего. Если вы решили изменить аккумулятор вместо его копирования, помните, что все равно нужно возвращать измененный объект в callback, иначе следующая итерация получит undefined. Однако, обратите внимание, что копирование аккумулятора может привести к увеличению использования памяти и снижению производительности — см. Когда не использовать reduce() для более подробной информации. В таких случаях, чтобы избежать плохой производительности и нечитаемого кода, лучше использовать цикл for.
Метод reduce() является универсальным. Он только ожидает, что значение this будет иметь свойство length и целочисленные свойства.
Крайние случаи
Если массив содержит только один элемент (независимо от его позиции) и initialValue не предоставлено, или если initialValue предоставлено, но массив пуст, единственное значение будет возвращено *без* вызова callbackFn.
Если initialValue предоставлено, а массив не пуст, то метод reduce всегда будет вызывать функцию обратного вызова, начиная с индекса 0.
Если initialValue не предоставлено, то метод reduce будет действовать по-разному для массивов длиной больше 1, равной 1 и 0, как показано в следующем примере:
const getMax = (a, b) => Math.max(a, b); // callback is invoked for each element in the array starting at index 0 [1, 100].reduce(getMax, 50); // 100 [50].reduce(getMax, 10); // 50 // callback is invoked once for element at index 1 [1, 100].reduce(getMax); // 100 // callback is not invoked [50].reduce(getMax); // 50 [].reduce(getMax, 1); // 1 [].reduce(getMax); // TypeError
Примеры
Как reduce() работает без начального значения
Приведенный ниже код показывает, что происходит, если вызвать reduce() с массивом и без начального значения.
const array = [15, 16, 17, 18, 19];
function reducer(accumulator, currentValue, index) {
const returns = accumulator + currentValue;
console.log(
`accumulator: ${accumulator}, currentValue: ${currentValue}, index: ${index}, returns: ${returns}`,
);
return returns;
}
array.reduce(reducer);
Callback будет вызван четыре раза, при этом аргументы и возвращаемые значения при каждом вызове будут следующими:
accumulator | currentValue | index | Возвращаемое значение | |
|---|---|---|---|---|
| Первый вызов | 15 | 16 | 1 | 31 |
| Второй вызов | 31 | 17 | 2 | 48 |
| Третий вызов | 48 | 18 | 3 | 66 |
| Четвертый вызов | 66 | 19 | 4 | 85 |
Параметр array никогда не меняется в процессе — он всегда равен [15, 16, 17, 18, 19]. Значением, возвращенным reduce(), будет значение последнего вызова callback (85).
Как reduce() работает с начальным значением
Здесь мы обрабатываем тот же массив, используя тот же алгоритм, но с initialValue, равным 10, переданным в качестве второго аргумента reduce():
[15, 16, 17, 18, 19].reduce( (accumulator, currentValue) => accumulator + currentValue, 10, );
Callback будет вызван пять раз, при этом аргументы и возвращаемые значения при каждом вызове будут следующими:
accumulator | currentValue | index | Возвращаемое значение | |
|---|---|---|---|---|
| Первый вызов | 10 | 15 | 0 | 25 |
| Второй вызов | 25 | 16 | 1 | 41 |
| Третий вызов | 41 | 17 | 2 | 58 |
| Четвертый вызов | 58 | 18 | 3 | 76 |
| Пятый вызов | 76 | 19 | 4 | 95 |
Значением, возвращенным reduce() в этом случае, будет 95.
Суммирование значений в массиве объектов
Чтобы просуммировать значения, содержащиеся в массиве объектов, вы должны предоставить initialValue, чтобы каждый элемент проходил через вашу функцию.
const objects = [{ x: 1 }, { x: 2 }, { x: 3 }];
const sum = objects.reduce(
(accumulator, currentValue) => accumulator + currentValue.x,
0,
);
console.log(sum); // 6
Последовательная передача функций
Функция pipe принимает последовательность функций и возвращает новую функцию. Когда новая функция вызывается с аргументом, последовательность функций вызывается по порядку, при этом каждая получает возвращаемое значение предыдущей функции.
const pipe =
(...functions) =>
(initialValue) =>
functions.reduce((acc, fn) => fn(acc), initialValue);
// Building blocks to use for composition
const double = (x) => 2 * x;
const triple = (x) => 3 * x;
const quadruple = (x) => 4 * x;
// Composed functions for multiplication of specific values
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);
// Usage
multiply6(6); // 36
multiply9(9); // 81
multiply16(16); // 256
multiply24(10); // 240
Последовательное выполнение промисов
Последовательность промисов — это, по сути, передача функций, продемонстрированная в предыдущем разделе, но выполненная асинхронно.
// Compare this with pipe: fn(acc) is changed to acc.then(fn),
// and initialValue is ensured to be a promise
const asyncPipe =
(...functions) =>
(initialValue) =>
functions.reduce((acc, fn) => acc.then(fn), Promise.resolve(initialValue));
// Building blocks to use for composition
const p1 = async (a) => a * 5;
const p2 = async (a) => a * 2;
// The composed functions can also return non-promises, because the values are
// all eventually wrapped in promises
const f3 = (a) => a * 3;
const p4 = async (a) => a * 4;
asyncPipe(p1, p2, f3, p4)(10).then(console.log); // 1200
asyncPipe также может быть реализован с использованием async/await, что лучше демонстрирует его сходство с pipe:
const asyncPipe =
(...functions) =>
(initialValue) =>
functions.reduce(async (acc, fn) => fn(await acc), initialValue);
Использование reduce() с разреженными массивами
reduce() пропускает отсутствующие элементы в разреженных массивах, но не пропускает значения undefined.
console.log([1, 2, , 4].reduce((a, b) => a + b)); // 7 console.log([1, 2, undefined, 4].reduce((a, b) => a + b)); // NaN
Вызов reduce() на не-массивных объектах
Метод reduce() считывает свойство length this, а затем получает доступ к каждому свойству, ключ которого является неотрицательным целым числом, меньшим length.
const arrayLike = {
length: 3,
0: 2,
1: 3,
2: 4,
3: 99, // ignored by reduce() since length is 3
};
console.log(Array.prototype.reduce.call(arrayLike, (x, y) => x + y));
// 9
Когда не использовать reduce()
Многоцелевые функции высшего порядка, такие как reduce(), могут быть мощными, но иногда трудными для понимания, особенно для менее опытных разработчиков JavaScript. Если код становится более понятным при использовании других методов массива, разработчики должны взвесить компромисс в читаемости по сравнению с другими преимуществами использования reduce().
Обратите внимание, что reduce() всегда эквивалентен циклу for...of, за исключением того, что вместо изменения переменной в верхней области видимости мы теперь возвращаем новое значение для каждой итерации:
const val = array.reduce((acc, cur) => update(acc, cur), initialValue);
// Is equivalent to:
let val = initialValue;
for (const cur of array) {
val = update(val, cur);
}
Как было сказано ранее, причина, по которой люди могут захотеть использовать reduce(), заключается в подражании практикам функционального программирования неизменяемых данных. Следовательно, разработчики, которые поддерживают неизменяемость аккумулятора, часто копируют весь аккумулятор для каждой итерации, например:
const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];
const countedNames = names.reduce((allNames, name) => {
const currCount = Object.hasOwn(allNames, name) ? allNames[name] : 0;
return {
...allNames,
[name]: currCount + 1,
};
}, {});
Этот код плохо работает, потому что каждая итерация должна копировать весь объект allNames, который может быть большим, в зависимости от количества уникальных имен. Этот код имеет наихудшую производительность O(N^2), где N — это длина names.
Лучшей альтернативой является *изменение* объекта allNames на каждой итерации. Однако, если allNames все равно изменяется, возможно, вы захотите вместо этого преобразовать reduce() в цикл for, который гораздо яснее:
const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];
const countedNames = names.reduce((allNames, name) => {
const currCount = allNames[name] ?? 0;
allNames[name] = currCount + 1;
// return allNames, otherwise the next iteration receives undefined
return allNames;
}, Object.create(null));
const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];
const countedNames = Object.create(null);
for (const name of names) {
const currCount = countedNames[name] ?? 0;
countedNames[name] = currCount + 1;
}
Следовательно, если ваш аккумулятор является массивом или объектом, и вы копируете массив или объект на каждой итерации, вы можете случайно ввести квадратичную сложность в ваш код, что приведет к быстрому снижению производительности на больших данных. Это происходило в реальном коде — см., например, Making Tanstack Table 1000x faster with a 1 line change.
Некоторые приемлемые варианты использования reduce() приведены выше (в частности, суммирование массива, последовательность промисов и передача функций). Существуют и другие случаи, когда существуют лучшие альтернативы reduce().
-
Разглаживание массива массивов. Вместо этого используйте
flat().const flattened = array.reduce((acc, cur) => acc.concat(cur), []);
const flattened = array.flat();
-
Группировка объектов по свойству. Вместо этого используйте
Object.groupBy().const groups = array.reduce((acc, obj) => { const key = obj.name; const curGroup = acc[key] ?? []; return { ...acc, [key]: [...curGroup, obj] }; }, {});const groups = Object.groupBy(array, (obj) => obj.name);
-
Объединение массивов, содержащихся в массиве объектов. Вместо этого используйте
flatMap().const friends = [ { name: "Anna", books: ["Bible", "Harry Potter"] }, { name: "Bob", books: ["War and peace", "Romeo and Juliet"] }, { name: "Alice", books: ["The Lord of the Rings", "The Shining"] }, ]; const allBooks = friends.reduce((acc, cur) => [...acc, ...cur.books], []);const allBooks = friends.flatMap((person) => person.books);
-
Удаление дублирующихся элементов в массиве. Вместо этого используйте
SetиArray.from().const uniqArray = array.reduce( (acc, cur) => (acc.includes(cur) ? acc : [...acc, cur]), [], );
const uniqArray = Array.from(new Set(array));
-
Удаление или добавление элементов в массив. Вместо этого используйте
flatMap().// Takes an array of numbers and splits perfect squares into its square roots const roots = array.reduce((acc, cur) => { if (cur < 0) return acc; const root = Math.sqrt(cur); if (Number.isInteger(root)) return [...acc, root, root]; return [...acc, cur]; }, []);const roots = array.flatMap((val) => { if (val < 0) return []; const root = Math.sqrt(val); if (Number.isInteger(root)) return [root, root]; return [val]; });Если вы только удаляете элементы из массива, вы также можете использовать
filter(). -
Поиск элементов или проверка, удовлетворяют ли элементы условию. Вместо этого используйте
find()иfindIndex(), илиsome()иevery(). Эти методы имеют дополнительное преимущество в том, что они возвращаются, как только результат становится известен, без итерации всего массива.const allEven = array.reduce((acc, cur) => acc && cur % 2 === 0, true);
const allEven = array.every((val) => val % 2 === 0);
В случаях, когда reduce() является лучшим выбором, документация и семантическое именование переменных могут помочь смягчить недостатки в читаемости.
Спецификации
Совместимость с браузерами
| Настольные | Мобильные | Сервер | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Opera | Safari | Chrome Android | Firefox for Android | Opera Android | Safari on iOS | Samsung Internet | WebView Android | WebView on iOS | Bun | Deno | Node.js | |
reduce |
3 |
12 |
3 |
10.5 |
4 |
18 |
4 |
14 |
3.2 |
1.0 |
4.4 |
3.2 |
1.0.0 |
1.0 |
0.10.0 |
См. также
- Полифилл
Array.prototype.reduceвcore-js - Полифилл
Array.prototype.reducees-shims - Руководство по индексированным коллекциям
ArrayArray.prototype.map()Array.prototype.flat()Array.prototype.flatMap()Array.prototype.reduceRight()TypedArray.prototype.reduce()Object.groupBy()Map.groupBy()
© 2005–2025 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce