解构 Map、Filter 和 Reduce
累加器
减少
地图
筛选
其他功能
额外积分
聚苯乙烯
概括
今天,我们将通过从头开始解构和重建来掌握map
、filter
和。reduce
我小时候收到一块手表作为礼物。让我妈妈大吃一惊的是,我做的第一件事就是抓起一把我能找到的最小的螺丝刀,把它一块一块地拆开。我想看看它的内部构造,检查每一个部件。
让我妈妈松了一口气的是,我终于把手表修好了,恢复了原来的状态。彻底检查了手表的内部结构后,我对手表的工作原理有了更深入的理解。
如今,我仍然喜欢把事物拆开来更好地理解。我也鼓励你也这样做。
我们先从外观开始reduce
。我一眼就能看出它有四个array
部分:method
reducer
initialValue
const items = [ 1, 2, 3, 4 ]
const initialValue = 0
const reducer = (accumulator, currentValue) => accumulator + currentValue
items.reduce(reducer, initialValue) //=> 10
/* \ \ \ \
array \ \ - initial value
method \
reducer
*/
一切都很容易理解。除了 之外的一切reducer
。这需要进一步分析。
注意:Reducers 有 4 个参数,现在我们将忽略最后 2 个并专注于accumulator
和currentValue
。
这些参数通常缩写为acc
和cur
。
const reducer = (acc, cur) => acc + cur
因为您已经熟悉 for 循环,所以我可以使用下面的 for 循环来帮助演示什么accumulator
是currentValue
和如何使用它们。
const items = [ 1, 2, 3, 4 ]
let acc = 0
// \
// initial value
for (let i = 0; i < items.length; i++) {
const cur = items[i]
// \
// current value
acc = acc + cur
// \
// update the accumulator
}
并插入reducer
...
for (let i = 0; i < items.length; i++) {
const cur = items[i]
acc = reducer(acc, cur)
}
如果您想查看更多类似的细分,请查看Map、Filter、Reduce 与 For 循环(语法)。
累加器
在上面的例子中,accumulator
是Number
,但它不一定是Number
,它可以是任何类型。
在这个例子中,acc
是一个,Array
并且reducer
将一个双倍的值推入accumulator
。
const items = [ 1, 2, 3, 4 ]
const reducer = (acc, cur) => {
acc.push(cur * 2)
return acc
/* \
The reducer must always return the accumulator
*/
}
let acc = []
for (let i = 0; i < items.length; i++) {
const cur = items[i]
acc = reducer(acc, cur)
}
acc //=> [ 2, 4, 6, 8 ]
在这个例子中,accumulator
是一个对象,并且新值被添加到该对象中。
const items = [ 1, 2, 3, 4 ]
const reducer = (acc, cur) => {
acc[cur] = cur * 2
return acc
}
let acc = {}
for (let i = 0; i < items.length; i++) {
const cur = items[i]
acc = reducer(acc, cur)
}
acc //=> { 1:2, 2:4, 3:6, 4:8 }
你应该注意到,这两个例子中的 for 循环代码完全相同。不信?往回翻一翻看看!只有initialValue
和reducer
改变了。所以,无论accumulator
是 a Number
、 an Array
、 an Object
,还是其他类型……你只需要修改initialValue
和reducer
,而不用修改循环!
减少
因为我们知道 for 循环永远不会改变,所以很容易将其提取到它自己的函数中reduce
。
const reduce = () => {
for (let i = 0; i < items.length; i++) {
const cur = items[i]
acc = reducer(acc, cur)
}
}
你的 linter 应该会报错缺失reducer
,items
所以我们来添加这些。我们还会添加一个initialValue
。
const reduce = (items, reducer, initialValue) => {
let acc = initialValue
for (let i = 0; i < items.length; i++) {
const cur = items[i]
acc = reducer(acc, cur)
}
return acc
}
就这样?我们刚刚创建了吗reduce
?看起来太简单了!
嗯,我们确实忽略了 中的那两个额外参数reducer
。另外,initialValue
inreduce
应该是可选的,但在我们的版本中是必需的。我们稍后会讨论这个问题。
地图
可以说map
是 的导数reduce
。在这种情况下,我们可以使用reducer
上面的 ,将其传递给reduce
,并提供 的初始值[]
。初始值为 ,[]
因为我们的结果将是Array
。
const map = (items, func) => {
// |
// function to modify value
const initialValue = []
const reducer = (acc, cur) => {
acc.push(func(cur))
// |
// execute func on the currentValue
return acc
}
return reduce(items, reducer, initialValue)
}
const double = x => x * 2
map(items, double) //=> [ 2, 4, 6, 8 ]
筛选
filter
几乎与完全相同map
。我们只需reducer
根据结果将过滤值更改为predicate
。
const filter = (items, predicate) => {
// |
// if truthy, append to accumulator
const initialValue = []
const reducer = (acc, cur) => {
if (predicate(cur)) {
// |
// run predicate on currentValue
acc.push(cur)
}
return acc
}
return reduce(items, reducer, initialValue)
}
const isEven = x => x % 2 === 0
filter(items, isEven) //=> [ 2, 4 ]
其他功能
in应该是可选的。我们应该能够做到这一点并得到 的结果,但我们得到的initialValue
是。reduce
10
NaN
const add = (acc, cur) => acc + cur
const items = [ 1, 2, 3, 4 ]
reduce(items, add) //=> NaN
你会如何将其变为initialValue
可选的?请在评论区展示你的代码。
我上面提到过,一个 Reducer 需要 4 个参数。这 4 个参数分别是:
- 累加器(累加器)
- 当前值(currentValue)
- 当前索引(currentIndex)
- 源数组(源)
我们已经实现了accumulator
和currentValue
。你会如何实现currentIndex
和source
?请在评论区展示你的代码。
额外积分
修改reduce
为同时适用于Array
和Iterator
。这是Array
reduce 无法做到的。
// range is an Iterator.
const range = require('mojiscript/list/range')
const reduce = (items, reducer, initialValue) => {
let acc = initialValue
for (let i = 0; i < items.length; i++) {
const cur = items[i]
acc = reducer(acc, cur)
}
return acc
}
const add = (acc, cur) => acc + cur
// Make this return 10
reduce(range(0)(5), add, 0)
创建一个reduceWhile
函数。它类似于reduce
,但需要添加一个额外的函数,当满足给定条件时,该函数会中断迭代。可以将其视为break
for 循环中的 。
const predicate = (acc, cur) => acc + cur < 7
const reduce = (items, predicate, reducer, initialValue) => {
/* solution goes here */
}
聚苯乙烯
本文以一种特定的方式对参数进行了排序,以便初学者更容易阅读。但如果我要将这些函数设计得更适合函数式编程 (FP),我会按如下方式对参数进行排序:
- 谓词
- 减速器
- 初始值
- 列表
概括
在解构map
、、filter
和reduce
了解其内在秘密之后,我们就能更容易地理解它们了。
显而易见,通过构建自己的reduce
,您可以扩展一些功能,例如能够支持Iterator
或 提前中断。我甚至进一步完善了MojiScript 的功能reduce
,使其不仅支持 ,async Iterator
还支持async reducer
。
您希望我更详细地讲解哪些内容?阅读本文后您学到了什么吗?请在评论区留言告诉我!
如果您喜欢函数式 JavaScript,请在这里或在 Twitter 上关注我@joelnet!
鏂囩珷鏉ユ簮锛�https://dev.to/joelnet/deconstructing-map-filter-and-reduce-1d1a