JavaScript Reduce使用详解

2022-04-15 0 1,022
目录
  • 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…

              以上就是JavaScript Reduce使用详解的详细内容,更多关于JavaScript Reduce使用的资料请关注NICE源码其它相关文章!

              免责声明:
              1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

              2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
              如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
              如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

              NICE源码网 JavaScript JavaScript Reduce使用详解 https://www.niceym.com/32074.html