自从2009年Ryan Dahl为了解决即时显示浏览器上传文件时的进度问题而开发了NodeJS开始,并在2010年1月诞生了大名鼎鼎的npm包管理工具之后,从此Javascript的发展就像是坐上了火箭一般,无数优秀的程序员通过Javascript为世界贡献了自己的创意,在现如今,不管程序员的主要工作是负责后端开发,还是Web开发,抑或是移动端开发,甚至在桌面端,应该都会在工作中使用到Javascript的,所以接下来的一段时间,Co哥想跟大家聊聊在Javascript最常用的数组中,Co哥总结的最为强大和实用的几个内置方法,希望可以帮助大家更好的解决在开发中遇到的相关问题,如果有表述不准确的地方,欢迎大家留言指正。
今天,我们先介绍Co哥最喜欢的两个成员:map和reduce,不过在正式介绍他们之前,得先介绍一下他们的长辈:遍历!毕竟,没有遍历,就没有一切!
遍历
让我们先从数组中最常用的一个场景说起,如果你是一个班级新来的班主任,要做的第一件事就需要把这个班级的所有学生熟悉一遍,你会拿着名单到课堂上点一遍名字,这就是遍历,在这个场景中,这个班级就是一个数组,而数组中的元素是每一个学生,而遍历就是把数组中的每个元素拿出来做一些事情,而且在这个过程中,每个元素只会出现一次。让我们用代码来演示一下最常用的三种遍历方式:
const students = ['Alex', 'Beryl', 'Charlotte']
// 方式1: forEach
students.forEach(student => console.log(student))
// 方式2: for...of
for(const student of students) {
console.log(student)
}
// 方式3: 原始for循环
for (let i = 0; i < students.length; i++) {
console.log(students[i])
}
可以看到,前两种方式更关注数组中的元素,而第三种方式优先关注的是元素的序号,所以,在日常的使用中,大多数的情况下,Co哥更推荐前两种方式。
简单的介绍完遍历,我们就可以进入正题啦,前面在遍历中说到,我们遍历数组的目的是为了使用数组中的元素做一些事情,编程语言在不断发展的过程中就发现,遍历中需要做的很多事情是有一些共同点的,所以,接下来我们要介绍的map/reduce其本质上就是将这些共同点进行标准化的结果。
map()
先给大家介绍一个Co哥最喜欢的方法map,map的中文意思是“映射”,用来对数组中的每个元素应用一个“魔法”,变成另外一个数组,听起来很强大有没有?魔术师最大的本领就是把一个东西变成另外一个东西,这样看来,你学会这个方法,你也是个魔术师了呀。
参数形式:
arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
第一个参数是一个函数,这个函数会对数组中每一个元素应用一次,并且这个函数必须有返回值,map方法会将函数应用在每个元素之后的返回值收集起来,生成一个新的数组,原理就是这样。
第二个参数为this作用域,可选,我们在此不做介绍。
通过一个简单的例子应该会更容易理解:
const nums = [1, 2, 3, 4, 5]
// 将nums中的每个数字转换为它的平方,结果:[1, 4, 9, 16, 25]
const square_nums = nums.map(num => num * num)
// 如果不使用map,使用for循环实现
const square_nums_for = []
for (let num of nums) {
square_nums_for.push(num * num)
}
在示例中,我们向map中送的参数是一个lambda匿名函数,在lambda中,因为只有一条语句,所以我们省略了return关键字,不过计算结果是会被返回的,lambda的内容,大家可以百度一下,比较简单,我们就不介绍了。从代码量可以看出来,map相比于for循环,简单明了,在实际的开发中,我们几乎每天都会遇到为数组中的每个元素执行某些运算,然后生出新数组的情况,强烈建议大家在这种情形下优先使用map,老话说「人生苦短,我用map」。
reduce()
reduce中文意思是“归约”,字面意思听起来有点抽象,举个例子,大家上学的时候肯定都做过阅读理解题,我们读完一篇文章之后,总是需要简短的总结一下文章主要在说什么对不对,这个过程其实就是reduce的过程,我们必须要读一遍(遍历)文章(数组),才能归纳出一个结果。
参数形式:
arr.reduce(function(previousValue, currentValue, currentIndex, array) {
/* ... */
}, initialValue)
第一个参数是一个称为reducer的函数,reduce会对数组中的每一个元素执行一次reducer函数,在reducer函数中,需要将运算结果返回,返回的运算结果会在对数组下一个元素执行reducer函数的时候作为第一个参数送入,也就是代码示例中的previousValue,reducer的第二个参数currentValue是当前元素的值,一般情况下,我们只需要关注这两个参数即可。
而reduce的第二个参数叫initialValue,含义是当reduce在数组的第一个元素上运行reducer函数的时候,作为previousValue的值,是个可选参数,如果不指定,那此时reducer函数的第一个参数previousValue就是数组的第一个元素也就是array[0]的值,而currentValue是数组的第二个元素也就是array[1]的值,这个部分一定要注意,让我们来一个实际的例子吧:
// 将数组的每一项加在一起
const nums = [1, 2, 3, 4, 5]
// 指定initialValue时
const sum1 = nums.reduce((previousValue, currentValue) => {
console.log(`previousValue: ${previousValue}, currentValue: ${currentValue}`)
return previousValue + currentValue
}, 0)
console.log(`sum1: ${sum1}`)
//控制台输出:
//previousValue: 0, currentValue: 1
//previousValue: 1, currentValue: 2
//previousValue: 3, currentValue: 3
//previousValue: 6, currentValue: 4
//previousValue: 10, currentValue: 5
//sum1: 15
//不指定initialValue时
const sum2 = nums.reduce((previousValue, currentValue) => {
console.log(`previousValue: ${previousValue}, currentValue: ${currentValue}`)
return previousValue + currentValue
})
console.log(`sum2: ${sum2}`)
//控制台输出:
//previousValue: 1, currentValue: 2
//previousValue: 3, currentValue: 3
//previousValue: 6, currentValue: 4
//previousValue: 10, currentValue: 5
//sum2: 15
这么强大的reduce一般用在什么情况下呢?相信大家看了上面的例子已经有答案了,那就是当你想把一个数组按某种方式变成一个值的时候,非常适合使用reduce,这也就是规约的体现,不过,还有一种情况,reduce的第二个参数initialValue也可以是一个对象或者数组,这种情况下,你最后归约的结果就是一个对象或者数组了,Co哥在之前就遇到过一种场景,接口返回的是一个数组,页面上需要显示数组中元素归类后的情况,结果需要是一个对象,这种情形下,reduce也非常适合,我们用代码让大家有更直观的体验:
//假设students是后台接口返回的数据
const students = [
{ name: '小明', gender: 'boy' },
{ name: '小强', gender: 'boy' },
{ name: '小花', gender: 'girl' },
{ name: '小天', gender: 'boy' }
]
//我们想按照gender分类,得到下面的结果:
// {boy: ['小明', '小强', '小天'], girl: ['小花']}
const result = students.reduce((acc, student) => {
let { name, gender } = student
return { ...acc, [gender]: [...(acc[gender] || []), name] }
}, {})
console.log(result)
在上面的例子中,reducer函数的返回值中我们使用了ES6的对象解构和Symbol机制,可以更方便的实现我们需要的数据结构,这部分内容我们之后会专门给大家介绍一下,大家也可以先去网上查查,很容易理解。
相信看到这里,大家已经基本明白map和reduce可以做什么了吧,其中有些详细的参数,Co哥是忽略掉的,因为80%的情况下你用不上,如果遇到需要使用的情况,去MDN查一下手册也就知道怎么用了,Co哥想分享给大家的是map/reduce的思想和常用场景,希望大家在日常的工作中可以用它们让复杂的事情变简单!
如果Co哥的文章对你有帮助,那就来个点赞+关注+转发吧!