JavaScript Reduce使用详解

作者:孟陬 时间:2024-04-19 10:16:03 

目录
  • map

  • filter

  • some

  • every

  • findIndex

  • pipe

    • 参考答案

      • 一、返回函数接受一个参数

      • 二、返回函数接受不定参数

  • 实现 lodash.get

    • 参考答案

    • 实现 lodash.flattenDeep

      • 过滤掉对象中的空值

        • enumify

          • Promise 串行执行器

            • 拓展

              JavaScript Reduce使用详解

              学会这一个技巧 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)

              其中一个难点在于判断 accaccumulation 的类型以及如何选择初始值,其实有个小技巧,可以帮助我们找到合适的初始值,我们想要的返回值的类型和 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

              标签:JavaScript,Reduce
              0
              投稿

              猜你喜欢

            • MySQL6.0新增特性

              2010-03-25 10:08:00
            • Python socket C/S结构的聊天室应用实现

              2023-08-01 05:06:38
            • Kettle连接Oracle数据库方法((Oracle19c&Oracle11g))

              2024-01-22 04:59:56
            • python实现弹跳小球

              2022-05-30 08:55:08
            • 装了 Access 2003 安全更新 (KB981716) 之后 Access 打不开

              2010-12-09 19:59:00
            • 浅谈Vue使用Cascader级联选择器数据回显中的坑

              2024-04-10 13:47:27
            • 如何将 awk 脚本移植到 Python

              2022-02-28 05:40:52
            • 详解Python的Django框架中的通用视图

              2021-09-21 23:34:21
            • 在Python中os.fork()产生子进程的例子

              2022-08-12 18:15:27
            • python中的文件打开与关闭操作命令介绍

              2021-07-05 10:15:39
            • Python爬取视频时长场景实践示例

              2021-08-14 01:32:56
            • C#编程实现连接ACCESS数据库实例详解

              2024-01-19 10:25:15
            • Insert into与AddNew哪一个更好?

              2009-10-28 18:30:00
            • Python 12306抢火车票脚本

              2023-09-12 13:36:11
            • python将YUV420P文件转PNG图片格式的两种方法

              2021-09-04 10:06:10
            • 基于Python实现船舶的MMSI的获取(推荐)

              2022-02-05 07:44:33
            • 用 Python 制作地球仪的方法

              2022-11-10 19:10:36
            • Python用正则表达式实现爬取古诗文网站信息

              2021-08-30 07:12:51
            • Python的列表推导式你了解吗

              2022-08-13 05:30:41
            • Python 数据库操作 SQLAlchemy的示例代码

              2024-01-28 04:42:28
            • asp之家 网络编程 m.aspxhome.com