318

假设我想a.xarr.

arr = [ { x: 1 }, { x: 2 }, { x: 4 } ];
arr.reduce(function(a, b){ return a.x + b.x; }); // => NaN

我有理由相信这a.xundefined在某个时候。

以下工作正常

arr = [ 1, 2, 4 ];
arr.reduce(function(a, b){ return a + b; }); // => 7

在第一个示例中我做错了什么?

4

19 回答 19

394

在第一次迭代之后,您将返回一个数字,然后尝试获取x它的属性以添加到下一个对象,该对象是和涉及结果的undefined数学。undefinedNaN

尝试返回一个对象,该对象包含一个x具有参数 x 属性总和的属性:

var arr = [{x:1},{x:2},{x:4}];

arr.reduce(function (a, b) {
  return {x: a.x + b.x}; // returns object with property x
})

// ES6
arr.reduce((a, b) => ({x: a.x + b.x}));

// -> {x: 7}

从评论中添加的解释:

每次迭代的返回值[].reduce用作a下一次迭代的变量。

迭代 1: a = {x:1}, b = {x:2},在迭代 2 中{x: 3}分配给a

迭代 2: a = {x:3}, b = {x:4}.

您的示例的问题是您返回的是数字文字。

function (a, b) {
  return a.x + b.x; // returns number literal
}

迭代 1: a = {x:1}, b = {x:2},// returns 3a下一次迭代一样

迭代 2: a = 3,b = {x:2}返回NaN

数字文字3没有(通常)有一个名为的属性x,所以它是undefined并且 undefined + b.x返回NaN并且NaN + <anything>总是NaN

澄清:我更喜欢我的方法而不是这个线程中的其他最佳答案,因为我不同意通过一个可选参数来减少一个幻数以得到一个数字原语更干净的想法。它可能会导致编写的行数减少,但 imo 可读性较差。

于 2011-04-20T14:34:53.853 回答
389

实现此目的的一种更简洁的方法是提供一个初始值作为 的第二个参数reduce

var arr = [{x:1}, {x:2}, {x:4}];
var result = arr.reduce(function (acc, obj) { return acc + obj.x; }, 0);
console.log(result);  // 7

第一次调用匿名函数时,它会被调用(0, {x: 1})并返回0 + 1 = 1。下一次,它被调用(1, {x: 2})并返回1 + 2 = 3。然后用 调用(3, {x: 4}),最后返回7

这也处理了数组为空的情况,返回0.

于 2011-06-09T23:57:18.597 回答
129

TL;DR,设置初始值

使用解构

arr.reduce( ( sum, { x } ) => sum + x , 0)

无需解构

arr.reduce( ( sum , cur ) => sum + cur.x , 0)

使用打字稿

arr.reduce( ( sum, { x } : { x: number } ) => sum + x , 0)

让我们尝试解构方法:

const arr = [ { x: 1 }, { x: 2 }, { x: 4 } ]
const result = arr.reduce( ( sum, { x } ) => sum + x , 0)
console.log( result ) // 7

关键是设置初始值。返回值成为下一次迭代的第一个参数。

最佳答案中使用的技术不是惯用的

接受的答案建议不传递“可选”值。这是错误的,因为惯用的方式是始终包含第二个参数。为什么?三个原因:

1. 危险 ——不传入初始值是危险的,如果回调函数不小心,可能会产生副作用和突变。

看哪

const badCallback = (a,i) => Object.assign(a,i) 

const foo = [ { a: 1 }, { b: 2 }, { c: 3 } ]
const bar = foo.reduce( badCallback )  // bad use of Object.assign
// Look, we've tampered with the original array
foo //  [ { a: 1, b: 2, c: 3 }, { b: 2 }, { c: 3 } ]

但是,如果我们以这种方式使用初始值:

const bar = foo.reduce( badCallback, {})
// foo is still OK
foo // { a: 1, b: 2, c: 3 }

作为记录,除非您打算改变原始对象,否则将第一个参数设置Object.assign为空对象。像这样:Object.assign({}, a, b, c)

2 - 更好的类型推断 ——当使用像 Typescript 这样的工具或像 VS Code 这样的编辑器时,你会得到告诉编译器初始值的好处,如果你做错了它可以捕获错误。如果您不设置初始值,在许多情况下它可能无法猜测,最终可能会出现令人毛骨悚然的运行时错误。

3 - 尊重 Functors—— 当 JavaScript 内部的函数子被释放时,它的表现最为出色。在函数世界中,有一个关于如何“折叠”或reduce数组的标准。当您折叠或对数组应用变态时,您可以使用该数组的值来构造一个新类型。您需要传达结果类型——即使最终类型是数组、另一个数组或任何其他类型中的值的类型,您也应该这样做。

让我们换一种方式考虑。在 JavaScript 中,函数可以像数据一样传递,这就是回调的工作原理,下面的代码的结果是什么?

[1,2,3].reduce(callback)

它会返回一个数字吗?一个东西?这使它更清楚

[1,2,3].reduce(callback,0)

在此处阅读有关函数式编程规范的更多信息:https ://github.com/fantasyland/fantasy-land#foldable

更多背景

reduce方法有两个参数,

Array.prototype.reduce( callback, initialItem )

callback函数采用以下参数

(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }

对于第一次迭代,

  • 如果initialItem提供,则reduce函数将 传递initialItem为 ,accumulator并将数组的第一项传递为itemInArray

  • 如果initialItem未提供,则该函数将数组中的第一项作为 the 传递,将数组中第二项传递为可能会造成混淆的行为。reduceinitialItemitemInArray

我教并建议始终设置 reduce 的初始值。

您可以在以下位置查看文档:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

希望这可以帮助!

于 2016-08-19T06:19:48.760 回答
37

其他人已经回答了这个问题,但我想我会采用另一种方法。您可以组合一个映射(从 ax 到 x)和 reduce(添加 x),而不是直接对 ax 求和:

arr = [{x:1},{x:2},{x:4}]
arr.map(function(a) {return a.x;})
   .reduce(function(a,b) {return a + b;});

诚然,它可能会稍微慢一些,但我认为值得一提的是它作为一个选项。

于 2011-04-21T15:21:09.913 回答
9

为了形式化已经指出的内容,reducer 是一种变态,它接受两个可能是巧合的相同类型的参数,并返回一个与第一个参数匹配的类型。

function reducer (accumulator: X, currentValue: Y): X { }

这意味着 reducer 的主体需要将currentValue的当前值转换accumulator为 new 的值accumulator

这在添加时以简单的方式工作,因为累加器和元素值都恰好是相同的类型(但用于不同的目的)。

[1, 2, 3].reduce((x, y) => x + y);

这很有效,因为它们都是数字。

[{ age: 5 }, { age: 2 }, { age: 8 }]
  .reduce((total, thing) => total + thing.age, 0);

现在我们给聚合器一个起始值。在绝大多数情况下,起始值应该是您期望聚合器的类型(您期望作为最终值出现的类型)。虽然您不是被迫这样做(也不应该这样做),但请务必牢记。

一旦知道这一点,您就可以为其他 n:1 关系问题编写有意义的缩减。

删除重复的单词:

const skipIfAlreadyFound = (words, word) => words.includes(word)
    ? words
    : words.concat(word);

const deduplicatedWords = aBunchOfWords.reduce(skipIfAlreadyFound, []);

提供找到的所有单词的计数:

const incrementWordCount = (counts, word) => {
  counts[word] = (counts[word] || 0) + 1;
  return counts;
};
const wordCounts = words.reduce(incrementWordCount, { });

将数组数组减少为单个平面数组:

const concat = (a, b) => a.concat(b);

const numbers = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
].reduce(concat, []);

任何时候,当您希望从一系列事物变为与 1:1 不匹配的单个值时,您可能会考虑使用 reduce。

事实上,map 和 filter 都可以实现为归约:

const map = (transform, array) =>
  array.reduce((list, el) => list.concat(transform(el)), []);

const filter = (predicate, array) => array.reduce(
  (list, el) => predicate(el) ? list.concat(el) : list,
  []
);

我希望这为如何使用reduce.

另外一个我还没有深入的补充是,当期望输入和输出类型特别是动态的时,因为数组元素是函数:

const compose = (...fns) => x =>
  fns.reduceRight((x, f) => f(x), x);

const hgfx = h(g(f(x)));
const hgf = compose(h, g, f);
const hgfy = hgf(y);
const hgfz = hgf(z);
于 2016-11-12T19:58:57.960 回答
6

对于第一次迭代,“a”将是数组中的第一个对象,因此 ax + bx 将返回 1+2 即 3。现在在下一次迭代中,返回的 3 被分配给 a,所以 a 是现在 n 调用 ax 的数字会给NaN。

简单的解决方案是首先映射数组中的数字,然后将它们减少如下:

arr.map(a=>a.x).reduce(function(a,b){return a+b})

这里arr.map(a=>a.x)将提供一个数字数组 [1,2,4] 现在使用.reduce(function(a,b){return a+b})将简单地添加这些数字而没有任何麻烦

另一个简单的解决方案是通过将 0 分配给“a”来提供初始总和为零,如下所示:

arr.reduce(function(a,b){return a + b.x},0)
于 2019-04-11T12:08:49.537 回答
5

在 reduce 的每一步,您都不会返回一个新{x:???}对象。所以你要么需要做:

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a + b.x})

或者你需要做

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; }) 
于 2011-04-20T14:35:42.600 回答
5

如果您有一个包含大量数据的复杂对象,例如对象数组,您可以逐步解决此问题。

例如:

const myArray = [{ id: 1, value: 10}, { id: 2, value: 20}];

首先,您应该将您的数组映射到您感兴趣的新数组中,在本例中它可能是一个新的值数组。

const values = myArray.map(obj => obj.value);

此回调函数将返回一个仅包含原始数组中的值的新数组,并将其存储在 values const 中。现在你的 values const 是一个像这样的数组:

values = [10, 20];

现在你已经准备好执行你的reduce了:

const sum = values.reduce((accumulator, currentValue) => { return accumulator + currentValue; } , 0);

可以看到,reduce 方法多次执行回调函数。对于每一次,它取数组中项目的当前值并与累加器求和。因此,要正确总结它,您需要将累加器的初始值设置为 reduce 方法的第二个参数。

现在你有了新的 const sum,值为 30。

于 2019-12-30T03:34:26.183 回答
3

在第一步中,它将正常工作,因为 的值为a1,而的值为b2,但由于 2+1 将被返回,并且在下一步中,的值b将是返回值from step 1 i.e 3,因此b.x将是未定义的。 .and undefined + anyNumber 将是 NaN,这就是你得到这个结果的原因。

相反,您可以通过将初始值设为零来尝试此操作,即

arr.reduce(function(a,b){return a + b.x},0);

于 2017-12-13T18:15:18.133 回答
3

我在 ES6 中做了一点改进:

arr.reduce((a, b) => ({x: a.x + b.x})).x

退货号码

于 2020-11-06T14:19:13.570 回答
2

我曾经遇到过这是我的开发,我所做的是将我的解决方案包装在一个函数中,使其在我的环境中可重用,如下所示:

const sumArrayOfObject =(array, prop)=>array.reduce((sum, n)=>{return sum + n[prop]}, 0)
于 2020-11-07T22:46:04.747 回答
0

reduce 函数迭代集合

arr = [{x:1},{x:2},{x:4}] // is a collection

arr.reduce(function(a,b){return a.x + b.x})

翻译为:

arr.reduce(
    //for each index in the collection, this callback function is called
  function (
    a, //a = accumulator ,during each callback , value of accumulator is 
         passed inside the variable "a"
    b, //currentValue , for ex currentValue is {x:1} in 1st callback
    currentIndex,
    array
  ) {
    return a.x + b.x; 
  },
  accumulator // this is returned at the end of arr.reduce call 
    //accumulator = returned value i.e return a.x + b.x  in each callback. 
);

在每次索引回调期间,变量“accumulator”的值被传递给回调函数中的“a”参数。如果我们不初始化“累加器”,它的值将是未定义的。调用 undefined.x 会给你错误。

要解决此问题,请使用值0初始化“累加器”,如上面显示的凯西的答案。

要了解“减少”功能的输入输出,我建议您查看该功能的源代码。Lodash 库具有 reduce 函数,其工作方式与 ES6 中的“reduce”函数完全相同。

这是链接: 减少源代码

于 2017-07-15T17:45:14.383 回答
0

返回所有x道具的总和:

arr.reduce(
(a,b) => (a.x || a) + b.x 
)
于 2019-08-12T20:24:58.403 回答
0

您可以使用如下reduce方法;如果您将 0(零)更改为 1 或其他数字,它会将其添加到总数中。例如,此示例给出的总数为 31,但是如果我们将 0 更改为 1,则总数将为 32。

const batteryBatches = [4, 5, 3, 4, 4, 6, 5];

let totalBatteries= batteryBatches.reduce((acc,val) => acc + val ,0)
于 2020-10-26T21:51:38.550 回答
0
function aggregateObjectArrayByProperty(arr, propReader, aggregator, initialValue) {
  const reducer = (a, c) => {
    return aggregator(a, propReader(c));
  };
  return arr.reduce(reducer, initialValue);
}

const data = [{a: 'A', b: 2}, {a: 'A', b: 2}, {a: 'A', b: 3}];

let sum = aggregateObjectArrayByProperty(data, function(x) { return x.b; }, function(x, y) { return x + y; }, 0);
console.log(`Sum = ${sum}`);
console.log(`Average = ${sum / data.length}`);

let product = aggregateObjectArrayByProperty(data, function(x) { return x.b; }, function(x, y) { return x * y; }, 1);
console.log(`Product = ${product}`);

刚刚从先前给出的解决方案中编写了一个通用函数。我是一名 Java 开发人员,因此对任何错误或非 JavaScript 标准表示歉意 :-)

于 2021-03-10T17:01:09.433 回答
0

用对象文字设置默认值只需我的 2 美分。

let arr = [{
    duration: 1
}, {
    duration: 3
}, {
    duration: 5
}, {
    duration: 6
}];

const out = arr.reduce((a, b) => {
    return {
        duration: a.duration + b.duration
    };
}, {
    duration: 0
});

console.log(out);

//输出

[object Object] {
    duration: 15
}
于 2021-11-11T13:41:51.087 回答
-1

数组reduce函数接受三个参数,即initialValue(默认为0)、累加器和当前值。默认情况下,initialValue 的值为 "0" 。由累加器获取

让我们在代码中看到这一点。

var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal ) ; 
// (remember Initialvalue is 0 by default )

//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks .
// solution = 7

现在与初始值相同的示例:

var initialValue = 10;
var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal,initialValue ) ; 
/
// (remember Initialvalue is 0 by default but now it's 10 )

//first iteration** : 10 +1 => Now accumulator =11;
//second iteration** : 11 +2 => Now accumulator =13;
//third iteration** : 13 + 4 => Now accumulator = 17;
No more array properties now the loop breaks .
//solution=17

同样适用于对象数组(当前的 stackoverflow 问题):

var arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(acc,currVal){return acc + currVal.x}) 
// destructing {x:1} = currVal;
Now currVal is object which have all the object properties .So now 
currVal.x=>1 
//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks 
//solution=7

要记住的一件事是 InitialValue 默认为 0 并且可以给出任何我的意思是 {}、[] 和数字

于 2018-12-21T21:56:28.383 回答
-1

    
  
 //fill creates array with n element
 //reduce requires 2 parameter , 3rd parameter as a length
 var fibonacci = (n) => Array(n).fill().reduce((a, b, c) => {
      return a.concat(c < 2 ? c : a[c - 1] + a[c - 2])
  }, [])
  console.log(fibonacci(8))

于 2019-03-25T05:52:44.647 回答
-2

您不应该使用 ax 作为 accumulator ,而是可以这样做 `arr = [{x:1},{x:2},{x:4}]

arr.reduce(函数(a,b){a + bx},0)`

于 2019-06-12T06:02:59.633 回答