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使用的资料请关注我们其它相关文章!

(0)

相关推荐

  • Javascript数组方法reduce的妙用之处分享

    前言 Javascript数组方法中,相比map.filter.forEach等常用的迭代方法,reduce常常被我们所忽略,今天一起来探究一下reduce在我们实战开发当中,能有哪些妙用之处,下面从reduce语法开始介绍. 语法 array.reduce(function(accumulator, arrayElement, currentIndex, arr), initialValue) 若传入初始值,accumulator首次迭代就是初始值,否则就是数组的第一个元素:后续迭代中将是上一

  • JavaScript中reduce()的5个基本用法示例

    前言 reduce()方法可以搞定的东西,for循环,或者forEach方法有时候也可以搞定,那为啥要用reduce()?这个问题,之前我也想过,要说原因还真找不到,唯一能找到的是:通往成功的道路有很多,但是总有一条路是最捷径的,亦或许reduce()逼格更高... 语法 arr.reduce(callback,[initialValue]) reduce()方法对数组中的每一个元素执行一个reducer函数(由你提供),从而得到一个单一的输出值. reduce() 方法将一个数组中的所有元素还

  • JS数组Reduce方法功能与用法实例详解

    本文实例讲述了JS数组Reduce方法功能与用法.分享给大家供大家参考,具体如下: 概述 一直以来都在函数式编程的大门之外徘徊,要入门的话首先得熟悉各种高阶函数,数组的reduce方法就是其中之一. reduce方法将会对数组元素从左到右依次执行reducer函数,然后返回一个累计的值.举个形象的例子:你要组装一台电脑,买了主板.CPU.显卡.内存.硬盘.电源...这些零件是组装电脑的必要条件. 装的过程可以简单概括为拆掉每个零件的包装,再装到一起.类比一下reduce函数就可以明白了,那些零件

  • 详解JavaScript之Array.reduce源码解读

    前言 reduce(...)方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值(累计作用) 此方法接受两个参数:callback(...)(必选).initialValue(可选). callback(...)接受4个参数:Accumulator (acc) (累计器).Current Value (cur) (当前值).Current Index (idx) (当前索引).Source Array (src) (源数组). 注意点: 1.callb

  • JavaScript之map reduce_动力节点Java学院整理

    如果你读过Google的那篇大名鼎鼎的论文"MapReduce: Simplified Data Processing on Large Clusters",你就能大概明白map/reduce的概念. map 举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个数组[1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map实现如下: 由于map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一

  • js数组方法reduce经典用法代码分享

    以下是个人在工作中收藏总结的一些关于javascript数组方法reduce的相关代码片段,后续遇到其他使用这个函数的场景,将会陆续添加,这里作为备忘. javascript数组那么多方法,为什么我要单挑reduce方法,一个原因是我对这个方法掌握不够,不能够用到随心所欲.另一个方面,我也感觉到了这个方法的庞大魅力,在许多的场景中发挥着神奇的作用. 理解reduce函数 reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值. a

  • js中的reduce()函数讲解

    定义: reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值.对空数组是不会执行回调函数的. 案例 1.数组求和 // 1.数组求和 var arr = [1,5,8,6,15,78,65,25,48,55] var sum = arr.reduce(function(total,currentValue){ return total+currentValue; }); console.log(sum);//306 var eachSum = 0;

  • JS数组reduce()方法原理及使用技巧解析

    1.语法 arr.reduce(callback,[initialValue]) reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组. callback (执行数组中每个值的函数,包含四个参数) 1.previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue)) 2.currentValue (数组中当前被处理的元素)

  • JS数组方法reduce的用法实例分析

    本文实例讲述了JS数组方法reduce的用法.分享给大家供大家参考,具体如下: 数组方法 reduce 用来迭代一个数组,并且把它累积到一个值中. 使用 reduce 方法时,你要传入一个回调函数,这个回调函数的参数是一个 累加器 (比如例子中的 previousVal) 和当前值 (currentVal). reduce 方法有一个可选的第二参数,它可以被用来设置累加器的初始值.如果没有在这定义初始值,那么初始值将变成数组中的第一项,而 currentVal 将从数组的第二项开始. 使用 re

  • 详解JS数组Reduce()方法详解及高级技巧

    基本概念 reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值. reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组. 语法: arr.reduce(callback,[initialValue]) callback (执行数组中每个值的函数,包含四个参数) previousValue (上

  • 无循环 JavaScript(map、reduce、filter和find)

    之前有讨论过,缩进(非常粗鲁地)增加了代码复杂性.我们的目标是写出复杂度低的 JavaScript 代码.通过选择一种合适的抽象来解决这个问题,可是你怎么能知道选择哪一种抽象呢?很遗憾的是到目前为止,没有找到一个具体的例子能回答这个问题.这篇文章中我们讨论不用任何循环如何处理 JavaScript 数组,最终得出的效果是可以降低代码复杂性. 循环是一种很重要的控制结构,它很难被重用,也很难插入到其他操作之中.另外,它意味着随着每次迭代,代码也在不断的变化之中.--Luis Atencio 我们先

随机推荐