受此答案启发,我后来扩展并撰写了一篇详细介绍此问题的博客文章。我建议您检查一下是否要对如何思考此问题有更深入的了解-我会尝试逐个进行解释,并在最后给出JSperf比较,并讨论速度方面的考虑。
也就是说,tl; dr是这样的:要完成您要的内容(在一个函数调用中进行过滤和映射),可以使用Array.reduce()
。
但是,更具可读性 和(次要地位)通常明显更快的方法2是只使用链接在一起的过滤器和地图:
[1,2,3].filter(num => num > 2).map(num => num * 2)
接下来是对Array.reduce()
工作方式的描述,以及如何使用它在一次迭代中完成过滤和映射。同样,如果过于简洁,我强烈建议您查看上面链接的博客文章,这是一个更加友好的介绍,其中包含清晰的示例和进度。
您提供reduce参数(通常是匿名函数)。
该匿名函数具有两个参数-一个参数(例如传递给map / filter / forEach的匿名函数)是要操作的迭代器。对于传递的匿名函数,还有另一种说法是减少这些函数不接受,这是将在函数调用之间传递的值,通常称为memo。
请注意,虽然Array.filter()仅接受一个参数(一个函数),但Array.reduce()也接受一个重要的(尽管可选)第二个参数:“ memo”的初始值将作为该参数传递给该匿名函数。第一个参数,然后可以在函数调用之间进行更改和传递。(如果未提供,则默认情况下,第一个匿名函数调用中的“ memo”将默认为第一个iteratee,而“ iteratee”自变量实际上将为数组中的第二个值)
在我们的例子中,我们将传入一个空数组开始,然后根据我们的功能选择是否将iteratee注入到我们的数组中-这是过滤过程。
最后,我们将在每个匿名函数调用中返回“进行中的数组”,reduce将获取该返回值并将其作为参数(称为备忘录)传递给下一个函数调用。
这样一来,过滤器和映射就可以在一次迭代中进行,从而将所需的迭代次数减少了一半-每次迭代只需做两倍的工作,因此,除了函数调用外,什么都没有真正节省下来,而在JavaScript中这并不是那么昂贵。
有关更完整的说明,请参阅MDN文档(或此答案开头引用的我的帖子)。
Reduce调用的基本示例:
let array = [1,2,3];
const initialMemo = [];
array = array.reduce((memo, iteratee) => {
if (iteratee > 1) {
memo.push(iteratee * 2);
}
return memo;
}, initialMemo)
console.log(array)
更简洁的版本:
[1,2,3].reduce((memo, value) => value > 1 ? memo.concat(value * 2) : memo, [])
请注意,第一个迭代器不大于一个,因此已被过滤。另请注意initialMemo,其名称仅是为了清楚说明其存在并引起注意。再次将其作为“ memo”传递给第一个匿名函数调用,然后将匿名函数的返回值作为“ memo”参数传递给下一个函数。
备忘录的经典用例的另一个示例是返回数组中的最小或最大数字。例:
[7,4,1,99,57,2,1,100].reduce((memo, val) => memo > val ? memo : val)
一个如何编写自己的reduce函数的示例(我发现这通常有助于理解这些函数):
test_arr = [];
test_arr.my_reducer = function(reduceFunc, initialMemo) {
const initialMemoIsIndexZero = arguments.length < 2;
let memo = initialMemoIsIndexZero ? this[0] : initialMemo;
const initialIteratee = initialMemoIsIndexZero ? 1 : 0;
for (var i = initialIteratee; i < this.length; i++) {
memo = reduceFunc(memo, this[i]);
}
return memo;
}
真正的实现允许访问诸如索引之类的内容,但是我希望这可以帮助您获得其要点的简单感觉。