数组的reduce方法的应用场景及相关面试题
reduce 最大的作用:从一个数组得到一个值,对数组中的每个元素执行 reducer 函数(升序执行)
reduce 打开相当于:
// 这里为了更加方便理解 统一加上initValue [x1, x2, x3].reduce(f,initValue) // f起码是两个参数 f(f(f(initValue,x1), x2), x3);
写reduce的核心其实很明了:
- 找到
initValue
- 找到
f
,f
始终有两个参数acc
和item
这样,能解决大部分的问题了,而且新手也有了线索,容易写reduce。
以下既是应用场景,也是很好练习的例子。
累加数组所有值
const sumFn = (acc, item) => acc + item; const sum = [1, 2, 3, 4].reduce(sumFn, 0); // 6 console.log(sum);
累加对象数组里的值
const sumFn = (acc, item) => acc + item.x; const sum = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }].reduce(sumFn, 0); // 6 console.log(sum);
降维数组,二维变成一维
// 之所以用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);
扁平化任意维数组 --- 面试常考
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), []);
计算数组中每个元素出现的次数 --- 面试常考
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 分类 --- 面试常考
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);
这里可以稍微升级下,可以对任意字段进行分类
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"));
使用扩展运算符处理对象数组中的数组
// 合并所有的书 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);
数组去重 --- 面试常考
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);
当然还有更简单粗暴的法子
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 --- 面试常考
// 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);
再抽象下写个通用的顺序执行的函数
const runOrderly = (arr, initValue) => arr.reduce((acc, item) => acc.then(item), Promise.resolve(initValue));
生成组合函数compose --- 面试常考
举个例子,就明白啥是组合函数了
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
// 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
// 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,这样每次的操作都是一致的不容易出错。本文也是如此操作