Array.prototype.reduce
是JS中一个很神奇的方法, 应该应用场景实在太多了, 比如 实现异步函数串行, 聚合数据等等。本文使用reduce来实现一下我们日常用使用到的工具函数, 希望每个例子都可以自己手动实现一遍, 习惯, 适应使用reduce来解决问题, 跟着JS标准更新自己的技术。
pipe 管道
pipe 管道可能是我们在linux下最常用的操作符之一了, 上一个函数的计算结果传给下一个函数, 就这样一直传递
应用举例: 当我们将函数拆分成更小粒度的时候, 我们可以用pipe将计算连接起来, 提高代码的可读性, 像下面这样的嵌套看着太累了, 写着也不舒服。
const calc1 = x => x * 2
const calc2 = x => x - 1
const calc3 = x => x * 3
const sum = calc3(calc2(calc1(10)))
使用pipe后
const calc = pipe(calc1, calc2, calc3)
const sum = calc(10)
使用 reduce 实现 pipe:
const pipe = (...functions) => (initialValue) =>
functions.reduce((value, fn) => fn(value), initialValue);
reduce是个迭代器, 接受2个参数, 第二个参数可选, 是开始迭代的值,如果不填默认从数组第一个值开始, 第一个参数是一个回调函数, 函数中的第一个值是上次计算结果, 第二个值是当前遍历的值, 这个解释有点抽象, 自己跟着实现几个工具函数就理解了。
compose 组合
compose和pipe非常的像, pipe是从左到右执行顺序, compose是从右向左的执行顺序, 所以在实现上用了reduceRight这个方法
const compose = (...functions) => (initialValue) =>
functions.reduceRight((value, fn) => fn(value), initialValue);
compact 去除假值
我们通常会使用filter
来去除假值
const falseyArray = [0, null, 1, undefined, 2, '', 3, false, 4, NaN]
const compact = (arr) => arr.filter(el => el)
compact(falseyArray) // [1, 2, 3, 4]
其实使用reduce也是可以的
const compact = list =>
list.reduce((acc, value) => {
value && acc.push(value)
return acc
}, [])
实现includes
includes这个函数在简化条件判断时候也很有用
const value = 'cat'
if (value === 'cat' || value === 'dog' || value === 'pig'){
// statement
}
// 可以该写为
if(['cat', 'dog', 'pig'].includes(value)){
// statement
}
使用reduce实现
const includes = (item, list) =>
list.reduce((isIncluded, value) => isIncluded || item === value, false);
flatten 扁平化数组
之前忘记从哪里看到过这么一道面试题, 是把嵌套数组扁平化, 也就是如何实现 Array.prototype.flatten
flatten([[1, 2], [3, 4]]); // [1, 2, 3, 4]
实现:
function flatten(arr){
return arr.reduce((pre, next) => {
return pre.concat(Array.isArray(next) ? flatten(next) : next)
}, [])
}
除了reduce实现外还有几种其他实现:
- 使用toString实现
[[[1, 2], [1, 2, 3]], [1, 2]].toString().split(',').map(Number)
- 使用解构提取
const flatten = list => {
while(list.some((item) => Array.isArray(item))){
list = [].concat(...list)
}
return list;
}
repeat
repeat是String
具有的方法, 但在处理数据时候我们经常会使用复杂点的数据类型比如 object, 这时候repeat就不能使用了, 我们可以用reduce来实现一个
const repeat = (item, times) =>
Array.from({ length: times }).reduce((acc) => {
acc.push(item);
return acc;
}, []);
// 使用
repeat({ favoriteLanguage: 'JavaScript' }, 2);
/*
[{
favoriteLanguage: 'JavaScript'
}, {
favoriteLanguage: 'JavaScript'
}],
*/
times 多次执行
有时候想让一个计算多次执行并返回一个列表, 实现和上面的repeat
很相似
times((x) => x * 2, 3);
// [0, 2, 4]
实现:
const times = (fn, numTimes) => Array.from({length: numTimes}).reduce((acc, _, index) => {
acc.push(fn(index))
return acc
}, [])
deduplicate 数组去重
面试题下次不让你用Set
特性去重的时候, 你就可以写这个
const deduplicate = (items) => {
const cache = {};
return items.reduce((acc, item) => {
const alreadyIncluded = cache[item] === true;
if (!alreadyIncluded) {
cache[item] = true;
acc.push(item);
}
return acc;
}, []);
};
deduplicate([[1], [1], { hello: 'world' }, { hello: 'world' }]);
// [[1], { hello: 'world' }]
reverse 逆序
reverse有个问题就是会改变原来的数组, 我们自己实现一个reverse来避免这种副作用
实现:
const reverse = list => list.reduceRight((acc, value) => {
acc.push(value)
return acc;
}, [])
reverse([1, 2, 3]);
// [3, 2, 1]
adjust
给数组中的指定位置的值应用某个函数, 比如 [1, -2, 3]
想给这个数组的第二个元素应用一个函数Math.abs()
校正一下这个数据。如果给定的数组index越界的话就返回原数组。
const adjust = (fn, list, index) => {
if(index > list.length) return list;
list.reduce((acc, value, i) => {
index === i ? acc.push(fn(value)) : acc.push(value)
return acc;
}, [])
}
fromPairs
函数作用:将一个pair数组转换成一个对象, 比如[['age', 20], ['name', 'evle']]
转换成 {name: 'evle', age: 10}
const fromPairs = list => list.reduce((acc, pairs) => {
const [k, v] = pairs;
acc[k] = v
return acc;
}, {})
range
js中没有range这种可以帮我们生成一个范围类型的工具函数比如: range(15, 20) // => 15, 16, 17, 18, 19, 20
, 那我们自己实现一个
const range = (start, end) => Array.from({length: end - start + 1}).reduce((acc, _, index) => {
acc.push(start + index)
return acc;
}, [])
insertAll
在一个数组的指定位置插入一个数组, 如果给定的位置越界的话就插在最后面
insertAll(1, [2, 3, 4], [1, 5]);
// [1, 2, 3, 4, 5]
insertAll(10, [2, 3, 4], [1, 5]);
// [1, 5, 2, 3, 4]
实现:
const insertAll = (index, subList, list) => {
if(list.length < index) {
return list.concat(subList)
}
return list.reduce((acc, value, i) => {
index === i ? acc.push(...subList, value) : acc.push(value)
return acc;
}, [])
}
mergeAll
把列表中的对象合并成一个对象
mergeAll([
{ js: 'reduce' },
{ elm: 'fold' },
{ java: 'collect' },
{ js: 'reduce' }
]);
/*
{
js: 'reduce',
elm: 'fold',
java: 'collect'
}
*/
实现:
const mergeAll = (list) => list.reduce((acc, value) => {
const [pair] = Object.entries(value)
const [k, v] = pair;
acc[k] = v;
return acc
}, {})
xprod
xprod 是将给定数组中的元素组合成各种结果, 比如
xprod(['Hello', 'World'], ['JavaScript', 'Reduce']);
/*
[
['Hello', 'JavaScript'],
['Hello', 'Reduce'],
['World', 'JavaScript'],
['World', 'Reduce']
]
*/
实现:
const xprod = (list1, list2) => list1.reduce((acc, value) => {
list2.forEach(el=>{
acc.push([value, el])
})
return acc
}, [])
intersperse
在数组的每个元素中间插入一个指定的元素
intersperse('Batman', [1, 2, 3, 4, 5, 6]);
// [1, 'Batman', 2, 'Batman', 3, 'Batman', 4, 'Batman', 5, 'Batman', 6]
intersperse('Batman', []);
// []
实现:
const intersperse = (spreator, list) => list.reduce((acc, value, index) => {
index === list.length - 1 ? acc.push(value) : acc.push(value, spreator)
return acc;
}, [])
insert
在数组的指定位置插入一个元素, 之前我们都是使用splice()
来在数组中插入元素的, 下面我们用reduce实现一个
insert(2, ['Batman'], [1, 2, 3]);
// [1, 2, ['Batman'], 3]
insert(10, ['Batman'], [1, 2, 3]);
// [1, 2, 3, ['Batman']]
实现:
const insert = (index, newItem, list) => {
if (index > list.length - 1) {
return [...list, newItem];
}
return list.reduce((acc, value, sourceArrayIndex) => {
index === sourceArrayIndex ? acc.push(newItem, value) : acc.push(value);
return acc;
}, []);
};
arrayIntoObject
有时候得到服务端的数据没有key, 数据没有任何规律, 为了方便操作我们一般会聚合数据
const users = [
{ username: 'JX01', status: 'offline' },
{ username: 'yazeedBee', status: 'online' }
];
// 实际上我们想要的是
{
JX01: {
username: 'JX01',
status: 'offline'
},
yazeedBee: { username: 'yazeedBee', status: 'online' }
}
// 或者
{
offline: {
username: 'JX01',
status: 'offline'
},
online: { username: 'yazeedBee', status: 'online' }
}
实现:
const arrayIntoObject = (key, list) =>
list.reduce((acc, obj) => {
const value = obj[key];
acc[value] = obj;
return acc;
}, {});