reduce & reduceRight 熟能生巧
在刚开始写代码的时候也会有很多的迭代方法例如 map forEach for…in.. for…of…等等
但是对于迭代方法中可以说的上最强的 reduce 却一知半解
无疑成为了一块心病,后来的工作渐渐让自己生成一种习惯,有迭代就问问自己能不能使用 reduce 或者说 reduce 会不会更方便
让我现在确实能够驾轻就熟了
tips: 另外确实还有帅 😬 也是进阶路上不可获取的;
本文会给出定义 给出常用用法 以及实际工作中作者真切用过的
定义
文档需要认真读清楚,不要给自己太多的惊喜或者某一天知道还能接收这样的参数这样的用法而感到惊讶
1 2 3
| Array.reduce((prev, cur, index, arr) => {}, initValue); Array.reduceRight((prev, cur, index, arr) => {}, initValue);
|
Array: 需要进行迭代操作的数组
callback:回调函数(必选)
- arr: 当前进行迭代操作的数组 实际上和 Array 是一样的
- prev: 表示上一次调用回调时的返回值,传入了 initValue 的时候 第一次的值就是 initValue
- cur: 表示当前正在处理的数组元素
- index: 表示正在处理的数组元素的索引,若提供 initValue 值,则索引起始为 0,否则索引起始值为 1
initValue: 表示初始值
简单来说 reduce 是将累计器(callback)分别作用于每一个数组迭代的成员上,把上一次的运行结果作为下一次输入的值。
难点一: 初始值
首先我们要明确的时候,pre 表示的是上一次回调时的返回值,或者是初始值 initValue。
在我们第一次调用的时候,在没有设置 initValue 值的情况下,index 的索引值是从 1 开始,
实际上是第一次运行时隐式调用了 pre+cur,
可以理解为是在背后做了 pre 是 0+cur:
1。我们在控制台看到的就是整个计算过程是 index 是 1-6。
在设置了 initValue 之后,那么就是代表 pre 的初始值就是 initValue,
第一次迭代执行的时候,index 是从 0 开始的,第一次调用返回的就是 2+arr[1]=3
整个过程 index 执行是从 0-6,共 7 次
常用方式
基本上平时需要使用 map 等迭代方法进行的 reduce 都能胜任 因此可以衍生出很多用法
累加|累乘求和
1 2 3 4 5 6 7 8 9 10 11
| const arr = [1, 2, 3, 4, 5, 6, 7];
const addSum = arr.reduce((pre, cur) => { return pre + cur; });
const multiplySum = arr.reduce((pre, cur) => { return pre * cur; });
|
计算数组中每个元素出现的次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const numArr = [1, 2, 3, 3, 5, 7, 8, 99, 1, 3, 7, 8, 8]; const numObj = numArr.reduce((pre, cur) => { if (cur in pre) { pre[cur]++; } else { pre[cur] = 1; } return pre; }, {}); console.log(numObj);
function Count(arr = []) { return arr.reduce((t, v) => ((t[v] = (t[v] || 0) + 1), t), {}); }
|
权重求和
1 2 3 4 5 6
| const scores = [ { score: 90, subject: 'chinese', weight: 0.3 }, { score: 95, subject: 'math', weight: 0.5 }, { score: 85, subject: 'english', weight: 0.2 }, ]; const result = scores.reduce((t, v) => t + v.score * v.weight, 0);
|
数组分割
1 2 3 4 5 6 7
| function Chunk(arr = [], size = 1) { return arr.length ? arr.reduce((t, v) => (t[t.length - 1].length === size ? t.push([v]) : t[t.length - 1].push(v), t), [[]]) : []; } const arr = [1, 2, 3, 4, 5]; Chunk(arr, 2);
|
数组成员位置记录
1 2 3 4 5 6
| function Position(arr = [], val) { return arr.reduce((t, v, i) => (v === val && t.push(i), t), []); }
const arr = [2, 1, 5, 4, 2, 1, 6, 6, 7]; Position(arr, 2);
|
数组成员特性分组
1 2 3 4 5 6 7 8 9 10 11
| function Group(arr = [], key) { return key ? arr.reduce((t, v) => (!t[v[key]] && (t[v[key]] = []), t[v[key]].push(v), t), {}) : {}; } const arr = [ { area: 'GZ', name: 'YZW', age: 27 }, { area: 'GZ', name: 'TYJ', age: 25 }, { area: 'SZ', name: 'AAA', age: 23 }, { area: 'FS', name: 'BBB', age: 21 }, { area: 'SZ', name: 'CCC', age: 19 }, ]; Group(arr, 'area');
|
数组成员所含关键字统计
1 2 3 4 5 6 7 8 9 10 11
| function Keyword(arr = [], keys = []) { return keys.reduce((t, v) => (arr.some((w) => w.includes(v)) && t.push(v), t), []); } const text = [ '今天天气真好,我想出去钓鱼', '我一边看电视,一边写作业', '小明喜欢同桌的小红,又喜欢后桌的小君,真TM花心', '最近上班喜欢摸鱼的人实在太多了,代码不好好写,在想入非非', ]; const keyword = ['偷懒', '喜欢', '睡觉', '摸鱼', '真好', '一边', '明天']; Keyword(text, keyword);
|
返回对象指定键值
1 2 3 4 5 6 7
| function GetKeys(obj = {}, keys = []) { return Object.keys(obj).reduce((t, v) => (keys.includes(v) && (t[v] = obj[v]), t), {}); }
const target = { a: 1, b: 2, c: 3, d: 4 }; const keyword = ['a', 'd']; GetKeys(target, keyword);
|
URL 参数反序列化
1 2 3 4 5 6 7 8 9 10 11 12
| function ParseUrlSearch() { return location.search .replace(/(^\?)|(&$)/g, '') .split('&') .reduce((t, v) => { const [key, val] = v.split('='); t[key] = decodeURIComponent(val); return t; }, {}); }
ParseUrlSearch();
|
URL 参数序列化
1 2 3 4 5 6 7
| function StringifyUrlSearch(search = {}) { return Object.entries(search) .reduce((t, v) => `${t}${v[0]}=${encodeURIComponent(v[1])}&`, Object.keys(search).length ? '?' : '') .replace(/&$/, ''); }
StringifyUrlSearch({ age: 27, name: 'YZW' });
|
数组转对象
1 2 3 4 5 6 7 8 9
| const people = [ { area: 'GZ', name: 'YZW', age: 27 }, { area: 'SZ', name: 'TYJ', age: 25 }, ]; const map = people.reduce((t, v) => { const { name, ...rest } = v; t[name] = rest; return t; }, {});
|
Redux Compose 原理
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Compose(...fns) { if (fns.length === 0) { return (arg) => arg; } if (fns.length === 1) { return fns[0]; } return fns.reduce( (t, v) => (...arg) => t(v(...arg)), ); }
|
异步累加
1 2 3 4 5 6 7 8 9
| async function AsyncTotal(arr = []) { return arr.reduce(async (t, v) => { const at = await t; const todo = await Todo(v); at[v] = todo; return at; }, Promise.resolve({})); } const result = await AsyncTotal();
|
强行 reduce
数组扁平化
1 2 3 4 5 6 7
| const numArr = [1, [2, [3, [4, 5]]], 6, [[[1, 2]], [3, 4]]]; const flatArr = (arr) => { return arr.reduce((pre, cur) => { return pre.concat(Array.isArray(cur) ? flatArr(cur) : cur); }, []); }; console.log(flatArr(numArr));
|
遇见数组扁平化的时候 还会有很多种方法
代替 Array.reverse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function Reverse1(arr = []) {
return arr.reduceRight((prev, cur) => (prev.push(cur), prev), []); }
function Reverse2(arr = []) { return arr.reduceRight((prev, cur) => [...prev, cur], []); }
function Reverse3(arr = []) { return arr.reduce((prev, cur) => [cur, ...prev], []); }
|
代替 map 和 filter
1 2 3 4 5 6 7 8 9 10 11 12 13
| const arr = [0, 1, 2, 3];
const a = arr.map((cur) => cur * 2); const b = arr.reduce((prev, cur) => [...prev, cur * 2], []);
const c = arr.filter((cur) => cur > 1); const d = arr.reduce((prev, cur) => (cur > 1 ? [...prev, cur] : prev), []);
const e = arr.map((cur) => cur * 2).filter((cur) => cur > 2); const f = arr.reduce((prev, cur) => (cur * 2 > 2 ? [...prev, cur * 2] : prev), []);
|
数组过滤
1 2 3 4 5 6
| function Difference(arr = [], carr = []) { return arr.reduce((t, v) => (!carr.includes(v) && t.push(v), t), []); } const arr1 = [1, 2, 3, 4, 5]; const arr2 = [2, 3, 6]; Difference(arr1, arr2);
|
数组去重
1 2 3 4 5 6
| function Uniq(arr = []) { return arr.reduce((t, v) => (t.includes(v) ? t : [...t, v]), []); }
const arr = [2, 1, 0, 3, 2, 1, 2]; Uniq(arr);
|
数组最大最小值
1 2 3 4 5 6 7 8 9 10
| function Max(arr = []) { return arr.reduce((t, v) => (t > v ? t : v)); }
function Min(arr = []) { return arr.reduce((t, v) => (t < v ? t : v)); } const arr = [12, 45, 21, 65, 38, 76, 108, 43]; Max(arr); Min(arr);
|
amazing
取子集
1
| [1, 2, 3, 4, 5, 6].reduce((t, item) => t.concat(t.map((v) => v.concat(item))), [[]]);
|
实际应用
生成 antd 中的 table columns
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const columns = () => Object.keys(TABLE_COL).reduceRight( (pre: ColumnsType, next: string) => [ { title: intl.formatMessage(tableCol_searchBar_msg[next]), width: 100, key: next, dataIndex: next, render: TABLE_COL[next] || ((val: unknown) => val ?? '-'), }, ...pre, ], [ { title: intl.formatMessage(globalMessages.operate), width: 100, dataIndex: 'action', key: 'action', fixed: 'right', render: (_value: unknown, row: ITimeRuleItem) => ( <> <TableButton onClick={() => openRulesModal(VIEW, row)}> {intl.formatMessage(globalMessages.detail)} </TableButton> <TableButton onClick={() => openRulesModal(EDIT, row)}> {intl.formatMessage(globalMessages.edit)} </TableButton> </> ), }, ], );
|
这是一段 tsx 代码目的是为了通过简单的配置 TABLE_COL 来生成 antd 中的 table columns;
本来只是简单的 reduce 就能完成 但问题需要在最后添加一列操作栏;
实际上使用 reduce 然后 push 一列进入数组也是能够达到目的的;
但那怎么能优雅呢 所以!如何把第一个默认元素成为最后一个元素?
reduceRight!YES!
问题又来了 那不是需要我把 TABLE_COL 对象里的每一个属性都倒过来才能按照正确的顺序渲染么?
兽人永不为奴!
1 2 3 4 5 6 7 8 9 10
| (pre: ColumnsType, next: string) => [ { title: intl.formatMessage(tableCol_searchBar_msg[next]), width: 100, key: next, dataIndex: next, render: TABLE_COL[next] || ((val: unknown) => val ?? '-'), }, ...pre, ],
|
只需要将遍历的元素在代码里面再倒回去就 OK 了
所以很简洁 nice!
参考文献:
作者:JowayYoung
链接:https://juejin.cn/post/6844904063729926152
来源:掘金