Array.prototype.reduce()
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
Array
实例的 reduce()
方法按顺序对数组的每个元素执行用户提供的 "reducer" 回调函数,并传入前一个元素计算的返回值。对数组的所有元素运行缩减程序的最终结果是单个值。
¥The reduce()
method of Array
instances executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.
第一次运行回调时没有 "上次计算的返回值"。如果提供的话,可以使用初始值来代替它。否则,索引 0 处的数组元素将用作初始值,并且迭代从下一个元素开始(索引 1 而不是索引 0)。
¥The first time that the callback is run there is no "return value of the previous calculation". If supplied, an initial value may be used in its place. Otherwise the array element at index 0 is used as the initial value and iteration starts from the next element (index 1 instead of index 0).
Try it
语法
参数
¥Parameters
callbackFn
-
对数组中的每个元素执行的函数。其返回值在下次调用
callbackFn
时成为accumulator
参数的值。对于最后一次调用,返回值变为reduce()
的返回值。使用以下参数调用该函数:accumulator
-
上次调用
callbackFn
所产生的值。第一次调用时,如果指定了后者,则其值为initialValue
;否则其值为array[0]
。 currentValue
-
当前元素的值。第一次调用时,如果指定了
initialValue
,则其值为array[0]
;否则其值为array[1]
。 currentIndex
-
currentValue
在数组中的索引位置。第一次调用时,如果指定了initialValue
,则其值为0
,否则为1
。 array
-
调用了数组
reduce()
。
initialValue
Optional-
第一次调用回调时
accumulator
被初始化的值。如果指定了initialValue
,则callbackFn
将从数组中的第一个值currentValue
开始执行。如果未指定initialValue
,则accumulator
会初始化为数组中的第一个值,而callbackFn
会以数组中的第二个值currentValue
开始执行。在这种情况下,如果数组为空(因此没有第一个值作为accumulator
返回),则会引发错误。
返回值
例外情况
描述
¥Description
reduce()
方法是 迭代法 方法。它按升序索引顺序对数组中的所有元素运行 "reducer" 回调函数,并将它们累积为单个值。每次,callbackFn
的返回值都会在下次调用时再次作为 accumulator
传递到 callbackFn
中。accumulator
的最终值(即数组最后一次迭代时从 callbackFn
返回的值)成为 reduce()
的返回值。请阅读 迭代法 部分,了解有关这些方法一般如何工作的更多信息。
¥The reduce()
method is an iterative method. It runs a "reducer" callback function over all elements in the array, in ascending-index order, and accumulates them into a single value. Every time, the return value of callbackFn
is passed into callbackFn
again on next invocation as accumulator
. The final value of accumulator
(which is the value returned from callbackFn
on the final iteration of the array) becomes the return value of reduce()
. Read the iterative methods section for more information about how these methods work in general.
callbackFn
仅针对已赋值的数组索引调用。稀疏数组 中的空槽不会调用它。
¥callbackFn
is invoked only for array indexes which have assigned values. It is not invoked for empty slots in sparse arrays.
与其他 迭代法 不同,reduce()
不接受 thisArg
参数。callbackFn
总是用 undefined
作为 this
来调用,如果 callbackFn
是非严格的,则用 globalThis
替换。
¥Unlike other iterative methods, reduce()
does not accept a thisArg
argument. callbackFn
is always called with undefined
as this
, which gets substituted with globalThis
if callbackFn
is non-strict.
reduce()
是 函数式编程 中的一个核心概念,其中不可能改变任何值,因此为了累积数组中的所有值,必须在每次迭代时返回一个新的累加器值。此约定传播到 JavaScript 的 reduce()
:你应该尽可能使用 spreading 或其他复制方法来创建新的数组和对象作为累加器,而不是改变现有的数组和对象。如果你决定改变累加器而不是复制它,请记住仍然在回调中返回修改后的对象,否则下一次迭代将收到未定义的信息。但是,请注意,复制累加器可能会导致内存使用量增加和性能下降 - 有关更多详细信息,请参阅 何时不使用 reduce()。在这种情况下,为了避免性能不佳和代码不可读,最好使用简单的 for
循环。
¥reduce()
is a central concept in functional programming, where it's not possible to mutate any value, so in order to accumulate all values in an array, one must return a new accumulator value on every iteration. This convention propagates to JavaScript's reduce()
: you should use spreading or other copying methods where possible to create new arrays and objects as the accumulator, rather than mutating the existing one. If you decided to mutate the accumulator instead of copying it, remember to still return the modified object in the callback, or the next iteration will receive undefined. However, note that copying the accumulator may in turn lead to increased memory usage and degraded performance — see When to not use reduce() for more details. In such cases, to avoid bad performance and unreadable code, it's better to use a simple for
loop instead.
reduce()
方法是 generic。它只期望 this
值具有 length
属性和整数键控属性。
¥The reduce()
method is generic. It only expects the this
value to have a length
property and integer-keyed properties.
边缘情况
¥Edge cases
如果数组只有一个元素(无论位置如何)并且没有提供 initialValue
,或者提供了 initialValue
但数组为空,则将返回 single 值,而不调用 callbackFn
。
¥If the array only has one element (regardless of position) and no initialValue
is provided, or if initialValue
is provided but the array is empty, the solo value will be returned without calling callbackFn
.
如果提供了 initialValue
并且数组不为空,则 reduce 方法将始终调用从索引 0 开始的回调函数。
¥If initialValue
is provided and the array is not empty, then the reduce method will always invoke the callback function starting at index 0.
如果未提供 initialValue
,那么对于长度大于 1、等于 1 和 0 的数组,reduce 方法的行为会有所不同,如下例所示:
¥If initialValue
is not provided then the reduce method will act differently for arrays with length larger than 1, equal to 1 and 0, as shown in the following example:
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() 如何工作
¥How reduce() works without an initial value
下面的代码显示了如果我们使用数组调用 reduce()
且没有初始值会发生什么。
¥The code below shows what happens if we call reduce()
with an array and no initial value.
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);
该回调将被调用四次,每次调用中的参数和返回值如下:
¥The callback would be invoked four times, with the arguments and return values in each call being as follows:
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()
返回的值将是最后一次回调调用 (85
) 的值。
¥The array
parameter never changes through the process — it's always [15, 16, 17, 18, 19]
. The value returned by reduce()
would be that of the last callback invocation (85
).
reduce() 如何使用初始值
¥How reduce() works with an initial value
在这里,我们使用相同的算法减少相同的数组,但将 10
的 initialValue
作为第二个参数传递给 reduce()
:
¥Here we reduce the same array using the same algorithm, but with an initialValue
of 10
passed as the second argument to reduce()
:
[15, 16, 17, 18, 19].reduce(
(accumulator, currentValue) => accumulator + currentValue,
10,
);
该回调将被调用五次,每次调用的参数和返回值如下:
¥The callback would be invoked five times, with the arguments and return values in each call being as follows:
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
。
¥The value returned by reduce()
in this case would be 95
.
对象数组中值的总和
¥Sum of values in an object array
要对对象数组中包含的值求和,你必须提供 initialValue
,以便每个项目都通过你的函数。
¥To sum up the values contained in an array of objects, you must supply
an initialValue
, so that each item passes through your function.
const objects = [{ x: 1 }, { x: 2 }, { x: 3 }];
const sum = objects.reduce(
(accumulator, currentValue) => accumulator + currentValue.x,
0,
);
console.log(sum); // 6
函数顺序管道
¥Function sequential piping
pipe
函数接受一系列函数并返回一个新函数。当使用参数调用新函数时,将按顺序调用函数序列,每个函数都会接收前一个函数的返回值。
¥The pipe
function takes a sequence of functions and returns a new function. When the new function is called with an argument, the sequence of functions are called in order, which each one receiving the return value of the previous function.
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
按顺序运行 Promise
¥Running promises in sequence
Promise 排序 本质上是上一节中演示的函数管道,只不过是异步完成的。
¥Promise sequencing is essentially function piping demonstrated in the previous section, except done asynchronously.
// 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
的相似性:
¥asyncPipe
can also be implemented using async
/await
, which better demonstrates its similarity with pipe
:
const asyncPipe =
(...functions) =>
(initialValue) =>
functions.reduce(async (acc, fn) => fn(await acc), initialValue);
将 reduce() 与稀疏数组结合使用
对非数组对象调用 reduce()
¥Calling reduce() on non-array objects
reduce()
方法读取 this
的 length
属性,然后访问键为小于 length
的非负整数的每个属性。
¥The reduce()
method reads the length
property of this
and then accesses each property whose key is a nonnegative integer less than 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()
¥When to not use reduce()
像 reduce()
这样的多用途高阶函数可能很强大,但有时很难理解,特别是对于经验不足的 JavaScript 开发者来说。如果使用其他数组方法时代码变得更清晰,开发者必须权衡可读性与使用 reduce()
的其他好处。
¥Multipurpose higher-order functions like reduce()
can be powerful but sometimes difficult to understand, especially for less-experienced JavaScript developers. If code becomes clearer when using other array methods, developers must weigh the readability tradeoff against the other benefits of using reduce()
.
请注意,reduce()
始终等效于 for...of
循环,只不过我们现在为每次迭代返回新值,而不是改变上部作用域中的变量:
¥Note that reduce()
is always equivalent to a for...of
loop, except that instead of mutating a variable in the upper scope, we now return the new value for each iteration:
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()
的原因是模仿不可变数据的函数式编程实践。因此,维护累加器不可变性的开发者通常会在每次迭代时复制整个累加器,如下所示:
¥As previously stated, the reason why people may want to use reduce()
is to mimic functional programming practices of immutable data. Therefore, developers who uphold the immutability of the accumulator often copy the entire accumulator for each iteration, like this:
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
的长度。
¥This code is ill-performing, because each iteration has to copy the entire allNames
object, which could be big, depending how many unique names there are. This code has worst-case O(N^2)
performance, where N
is the length of names
.
更好的选择是在每次迭代时改变 allNames
对象。但是,如果 allNames
无论如何都会发生变化,你可能需要将 reduce()
转换为简单的 for
循环,这样会更清晰:
¥A better alternative is to mutate the allNames
object on each iteration. However, if allNames
gets mutated anyway, you may want to convert the reduce()
to a simple for
loop instead, which is much clearer:
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;
}
因此,如果你的累加器是一个数组或一个对象,并且你在每次迭代时复制该数组或对象,则可能会意外地在代码中引入二次复杂度,导致在处理大数据时性能迅速下降。这在现实世界的代码中已经发生过 - 例如参见 通过 1 行更改使 Tanstack Table 速度提高 1000 倍。
¥Therefore, if your accumulator is an array or an object and you are copying the array or object on each iteration, you may accidentally introduce quadratic complexity into your code, causing performance to quickly degrade on large data. This has happened in real-world code — see for example Making Tanstack Table 1000x faster with a 1 line change.
上面给出了 reduce()
的一些可接受的用例(最值得注意的是,对数组求和、promise 排序和函数管道)。在其他情况下,存在比 reduce()
更好的替代方案。
¥Some of the acceptable use cases of reduce()
are given above (most notably, summing an array, promise sequencing, and function piping). There are other cases where better alternatives than reduce()
exist.
- 展平数组的数组。请改用
flat()
。jsconst flattened = array.reduce((acc, cur) => acc.concat(cur), []);
jsconst flattened = array.flat();
- 按属性对对象进行分组。请改用
Object.groupBy()
。jsconst groups = array.reduce((acc, obj) => { const key = obj.name; const curGroup = acc[key] ?? []; return { ...acc, [key]: [...curGroup, obj] }; }, {});
jsconst groups = Object.groupBy(array, (obj) => obj.name);
- 连接对象数组中包含的数组。请改用
flatMap()
。jsconst 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], []);
jsconst allBooks = friends.flatMap((person) => person.books);
- 删除数组中的重复项。请改用
Set
和Array.from()
。jsconst uniqArray = array.reduce( (acc, cur) => (acc.includes(cur) ? acc : [...acc, cur]), [], );
jsconst uniqArray = Array.from(new Set(array));
- 删除或添加数组中的元素。请改用
flatMap()
。js// 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]; }, []);
如果你只是从数组中删除元素,也可以使用jsconst 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()
。这些方法还有一个额外的好处,即一旦结果确定,它们就会返回,而无需迭代整个数组。jsconst allEven = array.reduce((acc, cur) => acc && cur % 2 === 0, true);
jsconst allEven = array.every((val) => val % 2 === 0);
如果 reduce()
是最佳选择,文档和语义变量命名可以帮助减轻可读性缺陷。
¥In cases where reduce()
is the best choice, documentation and semantic variable naming can help mitigate readability drawbacks.
规范
Specification |
---|
ECMAScript Language Specification # sec-array.prototype.reduce |
浏览器兼容性
BCD tables only load in the browser