假设我想对arr中的每个元素求和。
arr = [ { x: 1 }, { x: 2 }, { x: 4 } ];
arr.reduce(function(a, b){ return a.x + b.x; }); // => NaN
我有理由相信a。x在某些时候是没有定义的。
以下工作正常
arr = [ 1, 2, 4 ];
arr.reduce(function(a, b){ return a + b; }); // => 7
第一个例子中我做错了什么?
假设我想对arr中的每个元素求和。
arr = [ { x: 1 }, { x: 2 }, { x: 4 } ];
arr.reduce(function(a, b){ return a.x + b.x; }); // => NaN
我有理由相信a。x在某些时候是没有定义的。
以下工作正常
arr = [ 1, 2, 4 ];
arr.reduce(function(a, b){ return a + b; }); // => 7
第一个例子中我做错了什么?
当前回答
TL;DR,设置初始值
使用解构
加勒比海盗。Reduce ((sum, {x}) => sum + x, 0)
没有解构
加勒比海盗。Reduce ((sum, cur) => sum + cur.x, 0)
与打印稿
加勒比海盗。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) //
关键在于设置初始值。返回值将成为下一次迭代的第一个参数。
上面回答的技巧不是惯用的
接受的答案建议不传递“可选”值。这是错误的,因为惯用的方法是总是包括第二个参数。为什么?三个原因:
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的第一个参数。赋值给空对象。像这样:对象。赋值({},a, b, c)
2 -更好的类型推断 当你使用Typescript这样的工具或VS Code这样的编辑器时,你可以告诉编译器初始值,如果你做错了,它可以捕捉到错误。如果您不设置初始值,在许多情况下,它可能无法猜测,最终可能会出现令人毛骨悚然的运行时错误。
3 -尊重函子 当JavaScript内部的子函数被释放出来时,它的表现最好。在函数领域,有一个关于如何“折叠”或缩小数组的标准。当对数组折叠或应用变换时,将获取该数组的值来构造一个新类型。您需要传达结果类型——即使最终类型是数组、另一个数组或任何其他类型中的值的类型,您也应该这样做。
我们换个角度考虑。在JavaScript中,函数可以像数据一样传递,这就是回调的工作方式,下面的代码的结果是什么?
(1、2、3).reduce(回调)
它会返回一个数字吗?一个对象?这样更清楚
[1,2,3]。减少(回调,0)
在这里阅读更多函数式编程规范:https://github.com/fantasyland/fantasy-land#foldable
更多背景信息
reduce方法有两个参数,
Array.prototype.reduce( callback, initialItem )
回调函数接受以下参数
(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }
在第一次迭代中,
如果提供了initialItem,则reduce函数将initialItem作为累加器传递,并将数组的第一项作为itemInArray传递。 如果没有提供initialItem,则reduce函数将数组中的第一项传递为initialItem,将数组中的第二项传递为itemInArray,这可能会造成混淆。
我教授并建议设置reduce的初始值。
您可以在以下地址查看文档:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
其他回答
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"参数。如果不初始化"accumulator",它的值将是undefined。调用定义。X会给出误差。 要解决这个问题,初始化“accumulator”,值为0,如Casey的答案所示。
为了理解“reduce”函数的输入输出,我建议您查看该函数的源代码。 Lodash库有reduce函数,它的工作原理与ES6中的“reduce”函数完全相同。
以下是链接: 减少源代码
在第一次迭代之后,你返回一个数字,然后试图获得它的属性x,以添加到下一个对象,这是未定义的,数学涉及未定义的结果在NaN。
尝试返回一个包含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}, {x: 3}分配给迭代2中的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}, //在下一次迭代中返回3作为a
迭代2:a = 3, b = {x:2}返回NaN
数字文字3(通常)没有名为x的属性,因此它是未定义的,未定义+ b.x返回NaN, NaN + <任何>总是NaN
澄清:我更喜欢我的方法,而不是这个帖子中的其他答案,因为我不同意通过一个可选参数来减少一个神奇的数字来获得一个数字原语的想法。这可能会导致写的行数更少,但在我看来,可读性更差。
我们可以使用数组减少方法来创建新的对象,我们可以使用这个选项来求和或过滤
const FRUITS = ["apple", "orange"] const fruitBasket ={香蕉:{数量:10,kg:3},苹果:{数量:30,kg:10},橙子:{数量:1,kg:3}} const newFruitBasket =水果。Reduce ((acc, fruit) =>({…acc, [fruit]: fruitBasket[fruit]}), {}) console.log (newFruitBasket)
为了将所指出的内容形式化,reducer是一种变形,它接受两个可能碰巧是同一类型的参数,并返回与第一个参数匹配的类型。
function reducer (accumulator: X, currentValue: Y): X { }
这意味着减速器的主体需要将currentValue和累加器的当前值转换为新累加器的值。
这在添加时以一种简单的方式工作,因为累加器和元素值恰好是同一类型(但用途不同)。
[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);
我在ES6中做了一点改进:
arr.reduce((a, b) => ({x: a.x + b.x})).x
返回数