reduce 最大的作用:从一个数组得到一个值,对数组中的每个元素执行 reducer 函数(升序执行)
reduce 打开相当于:
```js
// 这里为了更加方便理解 统一加上initValue
[x1, x2, x3].reduce(f,initValue)
// f起码是两个参数
f(f(f(initValue,x1), x2), x3);
```
写reduce的核心其实很明了:
- 找到`initValue`
- 找到`f`,`f`始终有两个参数 `acc`和`item`
这样,能解决大部分的问题了,而且新手也有了线索,容易写reduce。
以下既是应用场景,也是很好练习的例子。
## 累加数组所有值
```js
const sumFn = (acc, item) => acc + item;
const sum = [1, 2, 3, 4].reduce(sumFn, 0);
// 6
console.log(sum);
```
## 累加对象数组里的值
```js
const sumFn = (acc, item) => acc + item.x;
const sum = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }].reduce(sumFn, 0);
// 6
console.log(sum);
```
## 降维数组,二维变成一维
```js
// 之所以用concat不用push 是concat返回合并后的数组,而push返回数组长度
const flat = (acc, item) => acc.concat(item);
const res = [
[0, 1],
[2, 3],
[4, 5]
].reduce(flat, []);
// [0, 1, 2, 3, 4, 5]
console.log(res);
```
## 扁平化任意维数组 --- 面试常考
```js
const flat = (acc, item) => acc.concat(item);
const res = [ 0, 1, [2, 3, [4, 5]] ].reduce(flat, []);
// [0, 1, 2, 3, 4, 5]
console.log(res)
// 这里更新了,稍微抽象出一个函数,当前项是数组的话 flatten(item) 不是数组的话用concat合并下
const flatten = arr =>
arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
```
## 计算数组中每个元素出现的次数 --- 面试常考
```js
const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];
const getCount = (obj, key) => {
key in obj || (obj[key] = 0);
obj[key]++;
return obj;
};
const res = names.reduce(getCount, {});
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
console.log(res);
```
## 按属性对 object 分类 --- 面试常考
```js
const people = [
{ id: 1, name: "Alice", age: 21 },
{ id: 2, name: "Max", age: 20 },
{ id: 3, name: "Jane", age: 20 }
];
const getType = (obj, item) => {
const key = item.age;
key in obj || (obj[key] = []);
obj[key].push(item);
return obj;
};
const res = people.reduce(getType, {});
// { 20: [ { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ], 21: [{ name: 'Alice', age: 21 }] }
console.log(res);
```
这里可以稍微升级下,可以对任意字段进行分类
```js
const classifyArray = (arr, keyClassified) => {
const getType = (obj, item) => {
const key = item[keyClassified];
key in obj || (obj[key] = []);
obj[key].push(item);
return obj;
};
return arr.reduce(getType, {});
};
const people = [
{ id: 1, name: "Alice", age: 21, gender: 0 },
{ id: 2, name: "Max", age: 20, gender: 1 },
{ id: 3, name: "Jane", age: 20, gender: 0 }
];
// { '0': [ { id: 1, name: 'Alice', age: 21, gender: 0 }, { id: 3, name: 'Jane', age: 20, gender: 0 } ], '1': [ { id: 2, name: 'Max', age: 20, gender: 1 } ] }
console.log(classifyArray(people, "gender"));
```
## 使用扩展运算符处理对象数组中的数组
```js
// 合并所有的书
var friends = [
{
name: "Anna",
books: ["Bible", "Harry Potter"],
age: 21
},
{
name: "Bob",
books: ["War and peace", "Romeo and Juliet"],
age: 26
},
{
name: "Alice",
books: ["The Lord of the Rings", "The Shining"],
age: 18
}
];
const sumArr = (oldArr, item) => {
return [...oldArr, ...item.books];
};
const res = friends.reduce(sumArr, []);
// [ 'Bible', 'Harry Potter', 'War and peace', 'Romeo and Juliet', 'The Lord of the Rings', 'The Shining' ]
console.log(res);
```
## 数组去重 --- 面试常考
```js
var myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
const addArr = (acc, item) => (acc.includes(item) ? acc : [...acc, item]);
const res = myArray.reduce(addArr, []);
// [ 'a', 'b', 'c', 'e', 'd' ]
console.log(res);
```
当然还有更简单粗暴的法子
```js
const unique = arr => Array.from(new Set(arr));
// [ 'a', 'b', 'c', 'e', 'd' ]
console.log(
unique(["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"])
);
```
## 按顺序运行 promise --- 面试常考
```js
// promise function 1
function p1(a) {
return new Promise(resolve => {
resolve(a * 5);
});
}
// !!这个是普通函数哟
function f(a) {
return a * 2;
}
// promise function 2
function p2(a) {
return new Promise(resolve => {
resolve(a - 2);
});
}
const arr = [p1, f, p2];
const runOrderly = (acc, item) => acc.then(item);
const res = arr.reduce(runOrderly, Promise.resolve(10));
// 98
console.log(res);
```
再抽象下写个通用的顺序执行的函数
```js
const runOrderly = (arr, initValue) =>
arr.reduce((acc, item) => acc.then(item), Promise.resolve(initValue));
```
## 生成组合函数compose --- 面试常考
举个例子,就明白啥是组合函数了
```js
const sum = (a, b) => a + b;
const len = str => str.length;
const addCurrency = str => "$" + str;
// 现在想要先 将两字符求和,然后求长度,再然后再长度前加个$,这就是一个新的函数,是已知函数的组合
const newFn = (a, b) => addCurrency(len(sum(a, b)));
console.log(newFn("xyz", "abc"));
```
但是不想像上面那样,地狱式嵌套的生成`newFn`,希望能 `compose(addCurrency,len,sum)`,这里可以试试`reduceRight`
```js
// compose(addCurrency,len,sum)
const compose = (...fns) => {
return (...args) => {
// 这里因为最后一个函数是两个参数,和别的不一样,所以这边单独把它扔出来,这样其他的都符合fn(acc)
const lastFn = fns.pop();
let initValue = lastFn(...args);
const f = (acc, fn) => fn(acc);
return fns.reduceRight(f, initValue);
};
};
const newFn = compose(addCurrency, len, sum);
console.log(newFn("xyz", "abc"));
// 用箭头函数简化
const compose = (...fns) => (...args) =>
fns.reduceRight((acc, fn) => fn(acc), fns.pop()(...args));
```
当然如果思维再厉害点,也可以用reduce
```js
// compose(addCurrency,len,sum)
const compose = (...fns) =>
fns.reduce((acc, cur) => (...args) => acc(cur(...args)));
```
这个法子很不容易想,但是假设现在只组合两个函数,len和sum
- compose就相当于`(...args) => len(sum(...args))`
- 而用reduce的话相当于`(...args)=>[len,sum].reduce((acc,curFn)=>(...args) => acc(curFn(...args)))`
- 将`[len,sum]`换成别的数组也一样
- 我这也是马后炮啦,下次我自己写估计又只会reduceRight
## reduce 的坑
- 空数组不能 reduce
- `reduce(f)`没 initValue 的话,f 的第一次参数是`arr[0] arr[1] 1 arr`
- `reduce(f,initValue)`有 initValue 的话,f 的第一次参数是`initValue arr[0] 0 arr`
- `f`必须至少有两个参数,总共四个参数`acc(累计器) cur(当前值) curIndex (当前索引) src (源数组)`
可读性更高的话,可以传入 initValue,这样每次的操作都是一致的不容易出错。本文也是如此操作
## 引用
- [mdn 的 map](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
- [廖雪峰的map解析](https://www.liaoxuefeng.com/wiki/1022910821149312/1024322552460832)
- [MapReduce: Simplified Data Processing on Large Clusters 原文](http://research.google.com/archive/mapreduce.html)
- [MapReduce翻译文](https://www.cnblogs.com/fuzhe1989/p/3413457.html)