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)
其中一个难点在于判断 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…
以上就是JavaScript Reduce使用详解的详细内容,更多关于JavaScript Reduce使用的资料请关注猪先飞其它相关文章!
相关文章
使用PHP+JavaScript将HTML页面转换为图片的实例分享
这篇文章主要介绍了使用PHP+JavaScript将HTML元素转换为图片的实例分享,文后结果的截图只能体现出替换的字体,也不能说将静态页面转为图片可以加快加载,只是这种做法比较interesting XD需要的朋友可以参考下...2016-04-19- 在昨天的《Javascript权威指南》学习笔记之十:ECMAScript 5 增强的对象模型一文中,对于一段代码的调试出现了一个奇怪现象,现将源代码贴在下面: 复制代码 代码如下: <script type="text/javascript"> function Person(){}...2014-05-31
- 最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
- 复制代码 代码如下: //element:需要添加新样式的元素,value:新的样式 function addClass(element, value ){ if (!element.className){ element.className = value; }else { newClassName = element.className; newClas...2014-05-31
- 在javascritp中,不一定只有对象方法的上下文中才有this, 全局函数调用和其他的几种不同的上下文中也有this指代。 它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下...2015-03-15
- 首先,我想到的是另建一个结果数组,用来存储原始数组中不重复的数据。遍历原始数组依次跟结果数组中的元素进行比较,检测是否重复。于是乎,我写出了如下代码A: Array.prototype.clearRepetitionA = function(){ var resul...2015-11-08
- 有一道js面试题,题目是这样的:下列代码的执行结果是什么,为什么? 复制代码 代码如下: var i, j, k; for (i=0, j=0; i<10, j<6; i++, j++) { k = i+j; } document.write(k); 答案是显示10,这道题主要考察JavaScript的逗...2015-03-15
- 事件触发器从字面意思上可以很好的理解,就是用来触发事件的,但是有些没有用过的朋友可能就会迷惑了,事件不是通常都由用户在页面上的实际操作来触发的吗?这个观点不完全正确,因为有些事件必须由程序来实现,如自定义事件,jQue...2014-06-07
- 这篇文章主要介绍了Javascript类型转换的规则实例解析,涉及到javascript类型转换相关知识,对本文感兴趣的朋友一起学习吧...2016-02-27
- 1、ActiveX向Javascript传参 复制代码 代码如下: <script language="javascript" for="objectname" event="fun1(arg)"> fun2(arg); </script> objectname为ActiveX控件名,通过<object>标签里的id属性设定,如下; 复制...2014-06-07
- 通过 HTML DOM,可访问 JavaScript HTML 文档的所有元素。 HTML DOM (文档对象模型) 当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。 HTML DOM 模型被构造为对象的树: 通过可编程的对象模型,Java...2015-10-23
- Window有navigator对象让我们得知浏览器的全部信息.我们可以利用一系列的API函数得知浏览器的信息.JavaScript代码如下:function message(){ txt = "<p>浏览器代码名: " + navigator.appCodeName + "</p>";txt+= "<p>...2015-11-24
- 虽然ES6都还没真正发布,但已经有用ES6重写的程序了,各种关于ES789的提议已经开始了,这你敢信。潮流不是我等大众所能追赶的。潮流虽然太快,但我们不停下学习的步伐,就不会被潮流丢下的,下面来领略下ES6中新特性,一堵新生代JS...2015-11-24
- 神马是“解释器模式”?先翻开《GOF》看看Definition:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。在开篇之前还是要科普几个概念: 抽象语法树: 解释器模式并未解释如...2014-06-07
- 这篇文章主要介绍了JavaScript预解析,对象的的相关资料,小编觉得这篇文章写的还不错,需要的朋友可以参考下,希望能够给你带来帮助...2021-11-10
- ---恢复内容开始---1.location.href.....(1)self.loction.href="http://www.cnblogs.com/url" window.location.href="http://www.cnblogs.com/url" 以上两个用法相同均为在当前页面打开URL页面 (2)this.locati...2015-10-30
- 这篇文章主要为大家介绍了JavaScript设计模式中的装饰者模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-21
- tab切换在网页中很常见,故最近总结了4种实现方法。 首先,写出tab的框架,加上最简单的样式,代码如下: <!DOCTYPE html> <html> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><style> *{ pa...2015-11-08
基于JavaScript如何实现私有成员的语法特征及私有成员的实现方式
前言在面向对象的编程范式中,封装都是必不可少的一个概念,而在诸如 Java,C++等传统的面向对象的语言中, 私有成员是实现封装的一个重要途径。但在 JavaScript 中,确没有在语法特性上对私有成员提供支持, 这也使得开发人员使...2015-10-30JavaScript学习笔记整理_setTimeout的应用
下面小编就为大家带来一篇JavaScript学习笔记整理_setTimeout的应用。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-03