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


Posted in Javascript onJune 10, 2019

前言

Javascript数组方法中,相比map、filter、forEach等常用的迭代方法,reduce常常被我们所忽略,今天一起来探究一下reduce在我们实战开发当中,能有哪些妙用之处,下面从reduce语法开始介绍。

语法

array.reduce(function(accumulator, arrayElement, currentIndex, arr), initialValue)

若传入初始值,accumulator首次迭代就是初始值,否则就是数组的第一个元素;后续迭代中将是上一次迭代函数返回的结果。所以,假如数组的长度为n,如果传入初始值,迭代次数为n;否则为n-1。

比如实现数组 arr = [1,2,3,4] 求数组的和

let arr = [1,2,3,4];
arr.reduce(function(pre,cur){return pre + cur}); // return 10

实际上reduce还有很多重要的用法,这是因为累加器的值可以不必为简单类型(如数字或字符串),它也可以是结构化类型(如数组或对象),这使得我们可以用它做一些其他有用的事情,比如:

  • 将数组转换为对象
  • 展开更大的数组
  • 在一次遍历中进行两次计算
  • 将映射和过滤函数组合
  • 按顺序运行异步函数

将数组转化为对象

在实际业务开发中,你可能遇到过这样的情况,后台接口返回的数组类型,你需要将它转化为一个根据id值作为key,将数组每项作为value的对象进行查找。

例如:

const userList = [
 {
 id: 1,
 username: 'john',
 sex: 1,
 email: 'john@163.com'
 },
 {
 id: 2,
 username: 'jerry',
 sex: 1,
 email: 'jerry@163.com'
 },
 {
 id: 3,
 username: 'nancy',
 sex: 0,
 email: ''
 }
];

如果你用过lodash这个库,使用_.keyBy这个方法就能进行转换,但用reduce也能实现这样的需求。

function keyByUsernameReducer(acc, person) {
 return {...acc, [person.id]: person};
}
const userObj = peopleArr.reduce(keyByUsernameReducer, {});
console.log(userObj);

将小数组展开成大数组

试想这样一个场景,我们将一堆纯文本行读入数组中,我们想用逗号分隔每一行,生成一个更大的数组名单。

const fileLines = [
 'Inspector Algar,Inspector Bardle,Mr. Barker,Inspector Barton',
 'Inspector Baynes,Inspector Bradstreet,Inspector Sam Brown',
 'Monsieur Dubugue,Birdy Edwards,Inspector Forbes,Inspector Forrester',
 'Inspector Gregory,Inspector Tobias Gregson,Inspector Hill',
 'Inspector Stanley Hopkins,Inspector Athelney Jones'
];

function splitLineReducer(acc, line) {
 return acc.concat(line.split(/,/g));
}
const investigators = fileLines.reduce(splitLineReducer, []);
console.log(investigators);
// [
// "Inspector Algar",
// "Inspector Bardle",
// "Mr. Barker",
// "Inspector Barton",
// "Inspector Baynes",
// "Inspector Bradstreet",
// "Inspector Sam Brown",
// "Monsieur Dubugue",
// "Birdy Edwards",
// "Inspector Forbes",
// "Inspector Forrester",
// "Inspector Gregory",
// "Inspector Tobias Gregson",
// "Inspector Hill",
// "Inspector Stanley Hopkins",
// "Inspector Athelney Jones"
// ]

我们从长度为5的数组开始,最后得到一个长度为16的数组。

另一种常见增加数组的情况是flatMap,有时候我们用map方法需要将二级数组展开,这时可以用reduce实现扁平化

例如:

Array.prototype.flatMap = function(f) {
 const reducer = (acc, item) => acc.concat(f(item));
 return this.reduce(reducer, []);
}

const arr = ["今天天气不错", "", "早上好"]

const arr1 = arr.map(s => s.split(""))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]

const arr2 = arr.flatMap(s => s.split(''));
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]

在一次遍历中进行两次计算

有时我们需要对数组进行两次计算。例如,我们可能想要计算数字列表的最大值和最小值。我们可以通过两次通过这样做:

const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4];
const maxReading = readings.reduce((x, y) => Math.max(x, y), Number.MIN_VALUE);
const minReading = readings.reduce((x, y) => Math.min(x, y), Number.MAX_VALUE);
console.log({minReading, maxReading});
// {minReading: 0.2, maxReading: 5.5}

这需要遍历我们的数组两次。但是,有时我们可能不想这样做。因为.reduce()让我们返回我们想要的任何类型,我们不必返回数字。我们可以将两个值编码到一个对象中。然后我们可以在每次迭代时进行两次计算,并且只遍历数组一次:

const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4];
function minMaxReducer(acc, reading) {
 return {
  minReading: Math.min(acc.minReading, reading),
  maxReading: Math.max(acc.maxReading, reading),
 };
}
const initMinMax = {
 minReading: Number.MAX_VALUE,
 maxReading: Number.MIN_VALUE,
};
const minMax = readings.reduce(minMaxReducer, initMinMax);
console.log(minMax);
// {minReading: 0.2, maxReading: 5.5}

将映射和过滤合并为一个过程

还是先前那个用户列表,我们希望找到没有电子邮件地址的人的用户名,返回它们用户名用逗号拼接的字符串。一种方法是使用两个单独的操作:

  • 获取过滤无电子邮件后的条目
  • 获取用户名并拼接

将它们放在一起可能看起来像这样:

function notEmptyEmail(x) {
 return !!x.email
}

function notEmptyEmailUsername(a, b) {
 return a ? `${a},${b.username}` : b.username
}

const userWithEmail = userList.filter(notEmptyEmail);
const userWithEmailFormatStr = userWithEmail.reduce(notEmptyEmailUsername, '');

console.log(userWithEmailFormatStr);
// 'john,jerry'

现在,这段代码是完全可读的,对于小的样本数据不会有性能问题,但是如果我们有一个庞大的数组呢?如果我们修改我们的reducer回调,那么我们可以一次完成所有事情:

function notEmptyEmail(x) {
 return !!x.email
}

function notEmptyEmailUsername(usernameAcc, person){
 return (notEmptyEmail(person))
  ? (usernameAcc ? `${usernameAcc},${person.username}` : `${person.username}`) : usernameAcc;
}

const userWithEmailFormatStr = userList.reduce(notEmptyEmailUsername, '');

console.log(userWithEmailFormatStr);
// 'john,jerry'

在这个版本中,我们只遍历一次数组,一般建议使用filter和map的组合,除非发现性能问题,才推荐使用reduce去做优化。

按顺序运行异步函数

我们可以做的另一件事.reduce()是按顺序运行promises(而不是并行)。如果您对API请求有速率限制,或者您需要将每个prmise的结果传递到下一个promise,reduce可以帮助到你。

举一个例子,假设我们想要为userList数组中的每个人获取消息。

function fetchMessages(username) {
 return fetch(`https://example.com/api/messages/${username}`)
  .then(response => response.json());
}

function getUsername(person) {
 return person.username;
}

async function chainedFetchMessages(p, username) {
 // In this function, p is a promise. We wait for it to finish,
 // then run fetchMessages().
 const obj = await p;
 const data = await fetchMessages(username);
 return { ...obj, [username]: data};
}

const msgObj = userList
 .map(getUsername)
 .reduce(chainedFetchMessages, Promise.resolve({}))
 .then(console.log);
// {glestrade: [ … ], mholmes: [ … ], iadler: [ … ]}

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

请注意,在此我们传递Promise作为初始值Promise.resolve(),我们的第一个API调用将立即运行。

下面是不使用async语法糖的版本

function fetchMessages(username) {
 return fetch(`https://example.com/api/messages/${username}`)
  .then(response => response.json());
}

function getUsername(person) {
 return person.username;
}

function chainedFetchMessages(p, username) {
 // In this function, p is a promise. We wait for it to finish,
 // then run fetchMessages().
 return p.then((obj)=>{
  return fetchMessages(username).then(data=>{
   return {
    ...obj,
    [username]: data
   }
  })
 })
}

const msgObj = peopleArr
 .map(getUsername)
 .reduce(chainedFetchMessages, Promise.resolve({}))
 .then(console.log);
// {glestrade: [ … ], mholmes: [ … ], iadler: [ … ]}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
中文输入法不触发onkeyup事件的解决办法
Jul 09 Javascript
js+canvas绘制矩形的方法
Jan 28 Javascript
jQuery height()、innerHeight()、outerHeight()函数的区别详解
May 23 Javascript
jQuery中的ready函数与window.onload谁先执行
Jun 21 Javascript
AngularJS基础 ng-value 指令简单示例
Aug 03 Javascript
jquery网页日历显示控件calendar3.1使用详解
Nov 24 Javascript
js中作用域的实例解析
Mar 16 Javascript
Vue computed计算属性的使用方法
Jul 14 Javascript
JS实现websocket长轮询实时消息提示的效果
Oct 10 Javascript
Node.Js中实现端口重用原理详解
May 03 Javascript
js实现同一个页面,多个enter事件绑定的示例
Oct 10 Javascript
Layui点击图片弹框预览的实现方法
Sep 16 Javascript
利用node 判断打开的是文件 还是 文件夹的实例
Jun 10 #Javascript
javascript function(函数类型)使用与注意事项小结
Jun 10 #Javascript
浅谈ECMAScript 中的Array类型
Jun 10 #Javascript
微信小游戏之使用three.js 绘制一个旋转的三角形
Jun 10 #Javascript
JavaScript变量作用域及内存问题实例分析
Jun 10 #Javascript
JavaScript内置对象math,global功能与用法实例分析
Jun 10 #Javascript
vue通信方式EventBus的实现代码详解
Jun 10 #Javascript
You might like
PHP自动生成后台导航网址的最佳方法
2013/08/27 PHP
zf框架db类的分页示例分享
2014/03/14 PHP
PHP文件上传类实例详解
2016/04/08 PHP
PHP删除数组中指定下标的元素方法
2018/02/03 PHP
360搜索引擎自动收录php改写方案
2018/04/28 PHP
小议Function.apply()之二------利用Apply的参数数组化来提高 JavaScript程序性能
2006/11/30 Javascript
jQuery Tips 为AJAX回调函数传递额外参数的方法
2010/12/28 Javascript
深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解
2013/05/08 Javascript
Javascript selection的兼容性写法介绍
2013/12/20 Javascript
JS实现至少包含字母、大小写数字、字符的密码等级的两种方法
2015/02/03 Javascript
JQuery EasyUI的使用
2016/02/24 Javascript
利用JQuery写一个简单的异步分页插件
2016/03/07 Javascript
微信小程序 限制1M的瘦身技巧与方法详解
2017/01/06 Javascript
Bootstrap显示与隐藏简单实现代码
2017/03/06 Javascript
Vue.js实战之组件之间的数据传递
2017/04/01 Javascript
基于jquery的on和click的区别详解
2018/01/15 jQuery
搭建vue开发环境
2018/07/19 Javascript
vue中使用echarts制作圆环图的实例代码
2018/07/27 Javascript
JavaScript中Array方法你该知道的正确打开方法
2018/09/11 Javascript
在vue中使用vue-echarts-v3的实例代码
2018/09/13 Javascript
ES6中Symbol、Set和Map用法详解
2019/08/20 Javascript
vue中的循环对象属性和属性值用法
2020/09/04 Javascript
Python中的FTP通信模块ftplib的用法整理
2016/07/08 Python
Python 转义字符详细介绍
2017/03/21 Python
Python编程之微信推送模板消息功能示例
2017/08/21 Python
TensorFlow实现非线性支持向量机的实现方法
2018/04/28 Python
Python3.6实现连接mysql或mariadb的方法分析
2018/05/18 Python
对python 合并 累加两个dict的实例详解
2019/01/21 Python
python 日期排序的实例代码
2019/07/11 Python
使用批处理脚本自动生成并上传NuGet包(操作方法)
2019/11/19 Python
python3 re返回形式总结
2020/11/20 Python
用html5的canvas和JavaScript创建一个绘图程序的简单实例
2016/07/06 HTML / CSS
医疗保健专业人士购物网站:Scrubs & Beyond
2017/02/08 全球购物
澳大利亚领先的在线葡萄酒零售商:Get Wines Direct
2018/03/27 全球购物
《窃读记》教学反思
2016/02/18 职场文书
导游词之云南丽江-泸沽湖
2019/09/26 职场文书