受写这个答案的启发,我后来扩展并写了一篇博客文章,详细讨论了这个问题。如果您想更深入地了解如何考虑这个问题,我建议您检查一下——我尝试逐段解释它,并在最后给出一个 JSperf 比较,超越速度考虑。
也就是说,** tl;dr 是这样的:
要完成您的要求(在一个函数调用中进行过滤和映射),您将使用Array.reduce()
**.
然而,更具可读性 和(不太重要)通常明显更快的2方法是仅使用链接在一起的过滤器和映射:
[1,2,3].filter(num => num > 2).map(num => num * 2)
以下是如何Array.reduce()
工作的描述,以及如何使用它在一次迭代中完成过滤和映射。同样,如果这太浓缩了,我强烈建议您查看上面链接的博客文章,这是一个更友好的介绍,带有清晰的示例和进展。
你给 reduce 一个参数,它是一个(通常是匿名的)函数。
该匿名函数有两个参数——一个(如传入 map/filter/forEach 的匿名函数)是要操作的迭代对象。然而,传递给 reduce 的匿名函数还有另一个参数,即那些函数不接受,这就是将在函数调用之间传递的值,通常称为memo。
请注意,虽然 Array.filter() 只接受一个参数(一个函数),但 Array.reduce() 还接受一个重要的(尽管是可选的)第二个参数:'memo' 的初始值,它将作为其传递给该匿名函数第一个参数,随后可以在函数调用之间进行变异和传递。(如果未提供,则第一个匿名函数调用中的 'memo' 默认为第一个 iteratee,而 'iteratee' 参数实际上是数组中的第二个值)
在我们的例子中,我们将传入一个空数组开始,然后根据我们的函数选择是否将我们的迭代器注入到我们的数组中——这就是过滤过程。
最后,我们将在每个匿名函数调用中返回我们的“正在进行的数组”,reduce 将获取该返回值并将其作为参数(称为 memo)传递给它的下一个函数调用。
这允许过滤器和映射在一次迭代中发生,将我们所需的迭代次数减少一半——虽然每次迭代只做两倍的工作,所以除了函数调用之外什么都没有真正保存,这在 javascript 中并不那么昂贵.
有关更完整的解释,请参阅MDN文档(或此答案开头引用的我的帖子)。
Reduce 调用的基本示例:
let array = [1,2,3];
const initialMemo = [];
array = array.reduce((memo, iteratee) => {
// if condition is our filter
if (iteratee > 1) {
// what happens inside the filter is the map
memo.push(iteratee * 2);
}
// this return value will be passed in as the 'memo' argument
// to the next call of this function, and this function will have
// every element passed into it at some point.
return memo;
}, initialMemo)
console.log(array) // [4,6], equivalent to [(2 * 2), (3 * 2)]
更简洁的版本:
[1,2,3].reduce((memo, value) => value > 1 ? memo.concat(value * 2) : memo, [])
请注意,第一个 iteratee 不大于 1,因此被过滤了。还要注意 initialMemo,命名只是为了明确它的存在并引起人们的注意。再一次,它作为“备忘录”传递给第一个匿名函数调用,然后匿名函数的返回值作为“备忘录”参数传递给下一个函数。
memo 的另一个经典用例示例是返回数组中的最小或最大数字。例子:
[7,4,1,99,57,2,1,100].reduce((memo, val) => memo > val ? memo : val)
// ^this would return the largest number in the list.
一个如何编写自己的 reduce 函数的示例(我发现这通常有助于理解这些函数):
test_arr = [];
// we accept an anonymous function, and an optional 'initial memo' value.
test_arr.my_reducer = function(reduceFunc, initialMemo) {
// if we did not pass in a second argument, then our first memo value
// will be whatever is in index zero. (Otherwise, it will
// be that second argument.)
const initialMemoIsIndexZero = arguments.length < 2;
// here we use that logic to set the memo value accordingly.
let memo = initialMemoIsIndexZero ? this[0] : initialMemo;
// here we use that same boolean to decide whether the first
// value we pass in as iteratee is either the first or second
// element
const initialIteratee = initialMemoIsIndexZero ? 1 : 0;
for (var i = initialIteratee; i < this.length; i++) {
// memo is either the argument passed in above, or the
// first item in the list. initialIteratee is either the
// first item in the list, or the second item in the list.
memo = reduceFunc(memo, this[i]);
// or, more technically complete, give access to base array
// and index to the reducer as well:
// memo = reduceFunc(memo, this[i], i, this);
}
// after we've compressed the array into a single value,
// we return it.
return memo;
}
例如,真正的实现允许访问诸如索引之类的东西,但我希望这可以帮助您对它的要点有一种简单的感觉。