很多人在开始熟练适用 JS 中的链式数组方法时都像打开了新世界。这是对函数式编程的初窥,而在使用如 [].map(..), [].filter(..) 和 [].reduce 时,reduce(..) 必然的成为三者之间理解时间最长的函数。
传统的链式调用会出现如下的情况:1
2
3
4
5
6
7
8
9
10
11
12const double = x => x * 2;
const addOne = x => x + 1;
const isSmartEnough = x => x >= 42;
const isNormalEnough = x => x <=100;
const sumReducer = (acc, item) => acc + item;
[4, 88, 41]
.map(double)
.map(addOne)
.filter(isSmartEnough)
.filter(isNormalEnough)
.reduce(sumReducer);
我们知道,每一个调用都会对原列表做一次循环,这里的一个显而易见的优化是:
1 | [4, 88, 41] |
通过对函数的组合,减少了循环的次数,看起来好多了。
但有些同学会思考,既然 map 可以被合并,那两个相同地 filter 能不能合并呢?就当前的方法来说,我们无法向 map 那样进行 compose 合并,因为每一个 filterer 的返回值都是 boolean。
船到桥头自然直
大家应该都试过用 reduce 来模拟 map 和 filter 的行为,而我们又希望能让 filter 和 reduce 能够合并,那么先将上面的两个 fielterer 转换成 reducer 来试一试
1 | const isSmartEnoughReducer = (list, item) => { |
等等,仔细一看,这俩货怎么长得这么像呢?进行进一步的抽象:
1 | const filterReducer = (predictFun) => { |
好多了。
但是总感觉 list.push(item) 这里不太符合纯函数的要求,如果把他提取出来呢?
1 | const combineListReducer = (list, item) => [...list, item]; |
你问我为什么要叫 combineListREDUCER?因为一个 reducer 不就长这样吗?=)
整理整理上面的代码,思考一下,见证奇迹的时刻就要来了
看到上面的奇迹了吗?
没有?
因为奇迹在下面
奇迹来了
1 | const combineListReducer = (list, item) => [...list, item]; |
What? 合并了?
Wait for a minite.
What????
是的,你没错,一个 reducer 可以被当作另一个 reducer 的 combineList 函数,因为他们长得完全一样呀~
再来看看我们一开始的调用方法变成了什么样子1
2
3
4
5
6
7
8
9
10const sumReducer = (acc, item) => acc + item;
const combineListReducer = (list, item) => [...list, item];
const isSmartEnoughReducer = filterReducer(isSmartEnough, combineListReducer);
const isNormalAndSmartEnoughReducer = filterReducer(isNormalEnough, isSmartEnoughReducer);
[4, 88, 41]
.map(compose(addOne, double))
.reduce(isNormalAndSmartEnoughReducer, [])
.reduce(sumReducer, 0);
请仔细观察 combineListReducer 和 sumReducer,你知道该怎样合并最后两句代码吗?
没错,sumReducer 和 combineListReducer 的功能是一样的!
扔掉 combineListReducer 吧!
1 | const sumReducer = (acc, item) => acc + item; |
再进一步
几乎完成了我们的目标,现在消化一下上面的东西,我们一口气将 map 也合并进去:
1 | const mapReducer = (mapperFn, combineList) => { |
完成。
低级趣味
骗你的,你没发现这里还有优化的空间吗??要不我干嘛还要写成 x, y, z ??如果我们把 mapReducer 和 filterReducer (其实应该叫 reducerGenerator) 柯里化了呢?1
2
3
4
5
6
7
8
9
10
11
12// (combineList) => (list, item) => list;
const xr = mapReducer(compose(addOne, double));
const yr = filterReducer(isSmartEnough);
const zr = filterReducer(isNormalEnough);
const composition = compose(
zr,
yr,
xr,
);
const finalReducer = composition(sumReducer);
让我们更正式一点,顺便回到我们的标题
1 | // (combineList) => (list, item) => list; |
Fin.
其实还没完。
这段优化的代码里有一个很大的问题,你能找出来吗?