JavaScript Reduce使用详解
作者:孟陬 发布时间:2024-04-19 10:16:03
目录
map
filter
some
every
findIndex
pipe
参考答案
一、返回函数接受一个参数
二、返回函数接受不定参数
实现 lodash.get
参考答案
实现 lodash.flattenDeep
过滤掉对象中的空值
enumify
Promise 串行执行器
拓展
学会这一个技巧 Reduce
让你开启编程新世界
Learning This Reduce
Skill and a Whole New World Will Open up for You 🎉
reduce
可谓是 JS 数组方法最灵活的一个,因为可以替代数组的其他方法,比如 map
/ filter
/ some
/ every
等,也是最难理解的一个方法,lodash 很多方法也可以用其实现,学会 reduce 将给与开发者另一种函数式(Functional)、声明式(Declarative)的视角解决问题,而不是以往的过程式(Procedual)或命令式(Imperative)
其中一个难点在于判断 acc
即 accumulation
的类型以及如何选择初始值,其实有个小技巧,可以帮助我们找到合适的初始值,我们想要的返回值的类型和 acc
类型需要是一样的,比如求和最终结果是数字,则 acc
应该是数字类型,故其初始化必定是 0
。
下面开始巩固对 reduce
的理解和用法。
map
根据小技巧,map
最终返回值是数组,故 acc
也应该是一个数组,初始值使用空数组即可。
/**
* Use `reduce` to implement the builtin `Array.prototype.map` method.
* @param {any[]} arr
* @param {(val: any, index: number, thisArray: any[]) => any} mapping
* @returns {any[]}
*/
function map(arr, mapping) {
return arr.reduce((acc, item, index) => [...acc, mapping(item, index, arr)], []);
}
测试
map([null, false, 1, 0, '', () => {}, NaN], val => !!val);
// [false, false, true, false, false, true, false]
filter
根据小技巧,filter
最终返回值也是数组,故 acc
也应该是一个数组,使用空数组即可。
/**
* Use `reduce` to implement the builtin `Array.prototype.filter` method.
* @param {any[]} arr
* @param {(val: any, index: number, thisArray: any[]) => boolean} predicate
* @returns {any[]}
*/
function filter(arr, predicate) {
return arr.reduce((acc, item, index) => predicate(item, index, arr) ? [...acc, item] : acc, []);
}
测试
filter([null, false, 1, 0, '', () => {}, NaN], val => !!val);
// [1, () => {}]
some
some
当目标数组为空返回 false
,故初始值为 false
。
function some(arr, predicate) {
return arr.reduce((acc, val, idx) => acc || predicate(val, idx, arr), false)
}
测试:
some([null, false, 1, 0, '', () => {}, NaN], val => !!val);
// true
some([null, false, 0, '', NaN], val => !!val);
// false
附带提醒,二者对结果没影响但有性能区别,acc 放到前面因为是短路算法,可避免无谓的计算,故性能更高。
acc || predicate(val, idx, arr)
和
predicate(val, idx, arr) || acc
every
every
目标数组为空则返回 true,故初始值为 true
function every(arr, predicate) {
return arr.reduce((acc, val, idx) => acc && predicate(val, idx, arr), true)
}
findIndex
findIndex
目标数组为空返回 -1,故初始值 -1。
function findIndex(arr, predicate) {
const NOT_FOUND_INDEX = -1;
return arr.reduce((acc, val, idx) => {
if (acc === NOT_FOUND_INDEX) {
return predicate(val, idx, arr) ? idx : NOT_FOUND_INDEX;
}
return acc;
}, NOT_FOUND_INDEX)
}
测试
findIndex([5, 12, 8, 130, 44], (element) => element > 8) // 3
pipe
一、实现以下函数
/**
* Return a function to make the input value processed by the provided functions in sequence from left the right.
* @param {(funcs: any[]) => any} funcs
* @returns {(arg: any) => any}
*/
function pipe(...funcs) {}
使得
pipe(val => val * 2, Math.sqrt, val => val + 10)(2) // 12
利用该函数可以实现一些比较复杂的处理过程
// 挑选出 val 是正数的项对其 val 乘以 0.1 系数,然后将所有项的 val 相加,最终得到 3
const process = pipe(
arr => arr.filter(({ val }) => val > 0),
arr => arr.map(item => ({ ...item, val: item.val * 0.1 })),
arr => arr.reduce((acc, { val }) => acc + val, 0)
);
process([{ val: -10 }, { val: 20 }, { val: -0.1 }, { val: 10 }]) // 3
二、实现以下函数,既能实现上述 pipe 的功能,而且返回函数接纳参数个数可不定
/**
* Return a function to make the input values processed by the provided functions in sequence from left the right.
* @param {(funcs: any[]) => any} funcs
* @returns {(args: any[]) => any}
*/
function pipe(...funcs) {}
使得以下单测通过
pipe(sum, Math.sqrt, val => val + 10)(0.1, 0.2, 0.7, 3) // 12
其中 sum
已实现
/**
* Sum up the numbers.
* @param args number[]
* @returns {number} the total sum.
*/
function sum(...args) {
return args.reduce((a, b) => a + b);
}
参考答案
一、返回函数接受一个参数
省略过滤掉非函数的 func 步骤
/**
* Return a function to make the input value processed by the provided functions in sequence from left the right.
* @param {(arg: any) => any} funcs
* @returns {(arg: any) => any}
*/
function pipe(...funcs) {
return (arg) => {
return funcs.reduce(
(acc, func) => func(acc),
arg
)
}
}
二、返回函数接受不定参数
同样省略了过滤掉非函数的 func 步骤
/**
* Return a function to make the input value processed by the provided functions in sequence from left the right.
* @param {Array<(...args: any) => any>} funcs
* @returns {(...args: any[]) => any}
*/
function pipe(...funcs) {
// const realFuncs = funcs.filter(isFunction);
return (...args) => {
return funcs.reduce(
(acc, func, idx) => idx === 0 ? func(...acc) : func(acc),
args
)
}
}
性能更好的写法,避免无谓的对比,浪费 CPU
function pipe(...funcs) {
return (...args) => {
// 第一个已经处理,只需处理剩余的
return funcs.slice(1).reduce(
(acc, func) => func(acc),
// 首先将特殊情况处理掉当做 `acc`
funcs[0](...args)
)
}
}
第二种写法的 funcs[0](...args)
这个坑要注意,数组为空就 * 了,因为空指针了。
实现 lodash.get
实现 get
使得以下示例返回 'hello world'
。
const obj = { a: { b: { c: 'hello world' } } };
get(obj, 'a.b.c');
函数签名:
/**
* pluck the value by key path
* @param any object
* @param keyPath string 点分隔的 key 路径
* @returns {any} 目标值
*/
function get(obj, keyPath) {}
参考答案
/**
* Pluck the value by key path.
* @param any object
* @param keyPath string 点分隔的 key 路径
* @returns {any} 目标值
*/
function get(obj, keyPath) {
if (!obj) {
return undefined;
}
return keyPath.split('.').reduce((acc, key) => acc[key], obj);
}
实现 lodash.flattenDeep
虽然使用 concat 和扩展运算符只能够 flatten 一层,但通过递归可以去做到深度 flatten。
方法一:扩展运算符
function flatDeep(arr) {
return arr.reduce((acc, item) =>
Array.isArray(item) ? [...acc, ...flatDeep(item)] : [...acc, item],
[]
)
}
方法二:concat
function flatDeep(arr) {
return arr.reduce((acc, item) =>
acc.concat(Array.isArray(item) ? flatDeep(item) : item),
[]
)
}
有趣的性能对比,扩展操作符 7 万次 1098ms,同样的时间 concat 只能执行 2 万次
function flatDeep(arr) {
return arr.reduce((acc, item) =>
Array.isArray(item) ? [...acc, ...flatDeep(item)] : [...acc, item],
[]
)
}
var arr = repeat([1, [2], [[3]], [[[4]]]], 20);
console.log(arr);
console.log(flatDeep(arr));
console.time('concat')
for (i = 0; i < 7 * 10000; ++i) {
flatDeep(arr)
}
console.timeEnd('concat')
function repeat(arr, times) { let result = []; for (i = 0; i < times; ++i) { result.push(...arr) } return result; }
过滤掉对象中的空值
实现
clean({ foo: null, bar: undefined, baz: 'hello' })
// { baz: 'hello' }
答案
/**
* Filter out the `nil` (null or undefined) values.
* @param {object} obj
* @returns {any}
*
* @example clean({ foo: null, bar: undefined, baz: 'hello' })
*
* // => { baz: 'hello' }
*/
export function clean(obj) {
if (!obj) {
return obj;
}
return Object.keys(obj).reduce((acc, key) => {
if (!isNil(obj[key])) {
acc[key] = obj[key];
}
return acc;
}, {});
}
enumify
将常量对象模拟成 TS 的枚举
实现 enumify
使得
const Direction = {
UP: 0,
DOWN: 1,
LEFT: 2,
RIGHT: 3,
};
const actual = enumify(Direction);
const expected = {
UP: 0,
DOWN: 1,
LEFT: 2,
RIGHT: 3,
0: 'UP',
1: 'DOWN',
2: 'LEFT',
3: 'RIGHT',
};
deepStrictEqual(actual, expected);
答案:
/**
* Generate enum from object.
* @see https://www.typescriptlang.org/play?#code/KYOwrgtgBAglDeAoKUBOwAmUC8UCMANMmpgEw5SlEC+UiiAxgPYgDOTANsAHQdMDmAChjd0GAJQBuRi3ZdeA4QG08AXSmIgA
* @param {object} obj
* @returns {object}
*/
export function enumify(obj) {
if (!isPlainObject(obj)) {
throw new TypeError('the enumify target must be a plain object');
}
return Object.keys(obj).reduce((acc, key) => {
acc[key] = obj[key];
acc[obj[key]] = key;
return acc;
}, {});
}
Promise 串行执行器
利用 reduce 我们可以让不定数量的 promises 串行执行,在实际项目中能发挥很大作用。此处不细讲,请参考我的下一篇文章 JS 请求调度器。
拓展
请使用 jest 作为测试框架,给本文的所有方法书写单测
更多习题见 github.com/you-dont-ne…
来源:https://juejin.cn/post/6936465094205243405


猜你喜欢
- 1.创建一个项目django-admin.py startproject HelloWorld2.进入HelloWorld项目,在manag
- 1.用CSS实现布局让我们一起来做一个页面,首先,我们需要一个布局。请使用CSS控制3个div,实现如下图的布局。考察应试者的基本布局知识—
- ChromeDriver 是 google 为网站开发人员提供的自动化测试接口,它是 selenium2 和 chrome浏览器 进行通信的
- 最近做拍卖小程序,里面有一个需求是监控拍卖时间,需要对时间进行动态的倒计时显示从构思开始,做这个倒计时也花了我4个小时多,也遇到了很多问题,
- 误区 #11:镜像在检测到故障后瞬间就能故障转移错误 数据库镜像的故障转移既可以自动发起,也可以手动发起
- 有框计算器这个计算器我们用到了Python自带的Tkinter库# 导入tkinter库import tkinter我们要对窗口进行一些基本
- 下面的教程总结了Javascript在网页定位方面的相关知识。一、网页的绝对大小和相对大小首先,要明确两个基本概念。一张网页的全部面积,就是
- 本文实例讲述了php计算函数执行时间的方法。分享给大家供大家参考。具体如下:我们可以通过在程序的前后分别记录开始和结束时间,两个时间差就是程
- <?php /* *文件名:linearList.php * 功能:数据结构线性表的顺序存储实现 * author:黎锦焕 * @co
- 索引是什么索引是一种特殊的文件,包含着对数据表里所有记录的引用指针。可以对表中的一列或多列创建索引,并指定索引的类型,各类索引有各自的数据结
- 本文我们来看一下如何使用 Python 将 QQ 好友头像拼成“五一快乐”四个字。我们可以将整个实现过程分为两步:爬取 QQ 好友头像、利用
- MySQL由于它本身的小巧和操作的高效, 在数据库应用中越来越多的被采用.我在开发一个P2P应用的时候曾经使用MySQL来保存P2P节点,由
- js汉字简繁转换源代码:<html> <head> <title>汉字简繁转换工具_asp之家</
- 今天写了个爬虫,在抓取数据的时候遇到一个问题,我觉得如果不注意,这个问题很容易被忽略,所以特意在博客记录下:问题描述:比如,我在提取信息时,
- J2ME是利用HttpConnection建立HTTP连接,然后获取数据,ASP也是利用HTTP协议,因而可以利用J2ME与ASP建立连接,
- 现在很多地方都需要用到关键词过滤功能。比如一般的服务器都不允许一些词出现在网页上,站长有时候会对在本网站发布信息的内容进行一个广告过滤等。雨
- 简单的并发控制利用 channel 的缓冲设定,我们就可以来实现并发的限制。我们只要在执行并发的同时,往一个带有缓冲的 chann
- 管理认证系统最简单的方法是通过管理界面。然而,当你需要绝对的控制权的时候,有一些低层 API 需要深入专研,我们将在下面的章节中讨论它们。创
- 对于经常需要表格头部不东,而列表可以滚动,多用于数据比较多的情况,方便查看<!DOCTYPE HTML PUBLIC "-/
- 写在前面最近在更新我服务器上的python以及pip版本的时候,碰见了令人头痛的问题,就是我执行了升级指令之后,升级也正常的Successf