JavaScript中数组方法reduce的使用

一、reduce是什么东西

1.MDN文档的描述

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

有点难理解,即使当学会使用reduce方法后再来看这个总结,还是难以理解

如果你已经了解reduce的基本语法,可以直接跳到第二大部分,从案例开始看

2.先看语法

先忘记以上的描述,首先reduce方法接收两个参数
let arr = [3, 2, 4]
let finalValue = arr.reduce(function(preValue, currentValue, currentIndex, array){}, prepreValue)

第一个参数是一个愉快的回调函数,如果你还不理解回调函数,可以先学习下回调函数再来看这个方法。这个回调函数可以接收四个参数前一个值(preValue)当前值(currentValue)当前值的下标(currentIndex)调用用这个方法的数组本身(array)),其中前两个参数是必须传的,后面两个根据需要来传。语言是苍白的,代码才是真理,即使我这么努力的去解释,你肯定还是一头雾水的!所以你先试着理解这几个参数接下来我会好好解释。

第二个参数我喜欢把他叫做上一个的上一个值(prepreValue),他实际上也可以理解为回调函数的一部分,或者回调函数是他的一部分!(所以有人称他为归并方法)

​ 最终这个方法会返回最后一次回调执行的结果finalValue !

reduce()方法会为数组中的每一个元素依次执行回调函数,会跳过数组中的空值

​ 汇总下来就是,每次执行回调函数都会拿上次的返回值作为下次回调函数的preValue,取数组的下一位作为回调函数的currentValue,当currentValue取到数组的最后一位时,执行结束,返回最后一次执行的结果。

​ 如果你只传一个回调函数,不传第二个参数,那么第一次执行时,回调函数会拿数组的第一位作为preValue,拿数组的第二位作为currentValue,然后将此次回调函数返回的结果作为第二次执行回调函数的preValue,拿数组的第三位作为currentValue。

​ 此时最妙的来了,如果你传了第二个参数,那么回调函数第一次执行时,会把这个preprevalue作为preValue,把数组的第一位作为currentValue。

3.分析代码来理解reduce

当只传递回调函数时
let arr = [2, 3, 4, 6]
let finalValue = arr.reduce(function (preValue, currentValue) {
  return preValue + currentValue
})
console.log(finalValue)
// 输出一个15,我们来追踪这段代码的执行过程
// 第一次执行,回调函数会把数组的第一位,也就是 2 当作 preValue, 把数组的第二位,也就是 3 // 当作 currentValue
// preValue  currentValue	return(preValue + currentValue)
// 		2		3				return 2+3 即 5 作为下次回调的 preValue
//		5		4				return 5+4 即 9 作为下次回调的 preValue
//		9		6				return 9+6 即 15 函数执行结束 返回15
// 当currentValue取到数组的最后一位时,执行结束,返回最后一次执行的结果
// 你会发现,你已经实现了数组求和的功能!!

当然这个return 返回的值是由你来决定的,你当然也可以让return 返回一个其他的东西,比如说 return 1,那最终结果就是1,如果说 return preValue * currentValue,那就实现了数组中各项的累乘

当传递第二个参数时
let arr = [2, 3, 4, 6]
let finalValue = arr.reduce(function (preValue, currentValue) {
  return preValue + currentValue
}, 8)
console.log(finalValue)
// 输出一个23,我们来追踪这段代码的执行过程
// 第一次执行,回调函数会把prepreValue,也就是 8 当作 preValue, 把数组的第一位,也就是 2 // 当作 currentValue
// preValue  currentValue	return(preValue + currentValue)
// 		8		2				return 8+2 即 10 作为下次回调的 preValue
//		10		3				return 10+3 即 9 作为下次回调的 preValue
//		13		4				return 13+4 即 9 作为下次回调的 preValue
//		17		6				return 17+6 即 23 函数执行结束 返回23
// 当currentValue取到数组的最后一位时,执行结束,返回最后一次执行的结果
如果数组中存在空值呢
let arr = [2,, 3, 4,,, 6]
let finalValue = arr.reduce(function (preValue, currentValue) {
  return preValue + currentValue
})
console.log(finalValue)
// 输出一个15

reduce方法在为数组中每一个元素执行回调函数时,会跳过数组中的空的位置

二、reduce有什么用(案例)

​ 如果你已经学了箭头函数,可以用箭头函数来实现,来让代码更加精简
无论代码有多长,只要我写成一行,那就是一行代码实现的(手动狗头)

以下案例并不一定是最适合使用reduce来实现的,但是我们拿来学习reduce还是比较合适的

有些案例我会给出reduce外的解决方案,如果对哪些地方比较有兴趣,可以留言给我,这样我就有动力写下一篇博客了

1.求数组中的最大值

​ 现在有一个数组,arr = [3, 5, 1, 8, 6],要找出他里面的最大值

let arr = [3, 5, 1, 8, 6]
let value = arr.reduce(function (preValue, nextValue) {
  return Math.max(preValue, nextValue)
})
console.log(value)
// 我们来分析以上代码实现过程
// preValue  currentValue	    return Math.max(preValue, nextValue)
// 		3		5				return 3和5的最大值 即5 作为下次回调的 preValue
//		5		1				return 5和1的最大值 即5 作为下次回调的 preValue
//		5		8				return 5和8的最大值 即8 作为下次回调的 preValue
//		8		6				return 8和6的最大值 即8 函数执行结束 返回8
//  此时value = 8 输出value

后面是用箭头函数写,好像更加优雅

let arr = [3, 5, 1, 8, 6]
let value = arr.reduce((preValue, nextValue) => Math.max(preValue, nextValue))
console.log(value)

当然不用reduce可以更简单,以下可以作为了解

let arr = [3, 5, 1, 8, 6]
// other:1
let value = Math.max(...arr)
console.log(value)
// other:2
let value2 = Math.max.apply(Math, arr)
console.log(value2)

2.实现数组去重

现在有一个数组,arr = [2, 3, 4, 2, 4, 7, 5],我们要去除数组中重复的值

let arr = [2, 3, 4, 2, 4, 7, 5]
// 我们先来写一个判断是否重复的函数
function some(arr, value) {
  for (let i = 0; i < arr.length; i++) {
    if (value === arr[i]) {
      // 有任意一个相等的就返回false,false就代表有重复的
      return false
    }
  }
  // 到这里返回true,代表没有任何重复的
  return true
}
function noRepeatArr(arr) {
  let finalArr = arr.reduce(function (preValue, nextValue) {
    if (some(preValue, nextValue)) {
      return preValue.concat(nextValue)
    } else {
      return preValue
    }
  }, [])
  return finalArr
}
console.log(noRepeatArr(arr))

分析下以上代码,这次我们用上了第二个参数,神奇的prepreValue

// 首先简单介绍下我们封装的some()函数,它可以判断一个值是否存在于一个数组
// 我们的目的是将数组中的元素放进新的数组返回,当不重复时就放进数组,重复时就跳过它
// 所以我们需要遍历原数组的数值来与新数组的每个值进行对比
// 旧的数组arr = [2, 3, 4, 2, 4, 7, 5]   新的数组为[] 也就是我们传入的第二个参数 []
// 第一次执行回调的时候,将prepreValue作为第一次回调的preValue,取数组中的第一位2作为currentValue
// 此时 preValue = [], currentValue = 2,接下来我们开始逐步执行
// 我们的处理逻辑就是 —— 如果当前执行回调的时候,currentValue存在于preValue,那就直接返回								  preValue。如果不存在,就把currentValue拼接到preValue再返回
//	preValue  currentValue	第一次执行时很显然,2肯定不属于空数组,那就把2拼进[]
// 	[]			 2		return [].concat(2) 即[2] 作为下次回调的 preValue
//	[2]			 3		return [2].concat(3) 即[2,3] 作为下次回调的 preValue
//	[2,3]		 4		return [2,3].concat(4) 即[2,3,4] 作为下次回调的 preValue
//	[2,3,4]      2		return [2,3,4]即[2,3,4] 作为下次回调的 preValue
//	[2,3,4]      4		return [2,3,4]即[2,3,4] 作为下次回调的 preValue
//	[2,3,4]      7		return [2,3,4].concat(7)即[2,3,4,7] 作为下次回调的 preValue
//	[2,3,4,7]    5		return [2,3,4,7,5] 函数执行结束,将结果赋值给 finalArr并返回finalArr
//  此时finalArr = [2,3,4,7,5] 输出[2,3,4,7,5]

​ 现在使用愉快的箭头函数及some方法帮我们实现

// 当然了这么常用的判断函数,JS原生也封装了一个some()方法,可以让我们的代码更加精简
let arr = [2, 3, 4, 2, 4, 7, 5]
function noRepeatArr(arr) {
  return arr.reduce((preValue, nextValue) => {
    if (preValue.some(value => value === nextValue)) {
      return preValue
    }
    return preValue.concat(nextValue)
  }, [])
}
console.log(noRepeatArr(arr))

​ 当然了说到数组去重你可能可以列举出十几种方法,在这里我们就不一一拓展了

3.实现数组降维(递归)

​ 现在有一个数组 arr = [2, 3, 4, [4, 7, 5, [3, 4]], 2, 4, 7, 5],它很乱,不怎么规律,我们把他处理成一维数组

​ 因为数组的维度可能是未知的,我们又知道最终是要拿到一个一维的数组,是不是感觉很适合用递归来做!

恭喜你答对了,这么一分析,我们找到了规律,找到了递归的出口,也就是说,递归需要的条件我们都具备了

let arr = [2, 4, [4, 5, [3, 4]], [2, 4], 7, 5]
function flatArr(arr) {
  let newArr = arr.reduce(function (preValue, currentValue) {
    if (Array.isArray(currentValue)) {
      return preValue.concat(flatArr(currentValue))
    }
    return preValue.concat(currentValue)
  }, [])
  return newArr
}
console.log(flatArr(arr))

在看下面分析之前,你先记住一句话,递归的终极奥义就是放弃追踪递归的全程,那是计算机干的事

// 我们先写一个函数flatArr(arr)
// 我们的reduce可以拿到原始arr数组的每一项,
// 我们需要将数组中的每一个数字类型的放进新的数组
// 把每一个数组类型也分别进行降维操作后再与前面的拼接起来

// 我们先看函数第一次执行会发生什么
//	preValue  currentValue	第一次执行时很显然,2是数值类型,那就把2拼进[]
// 	[]			 2			return [].concat(2) 即[2] 作为下次回调的 preValue
// 	[2]			 4			return [2].concat(4) 即[2,4] 作为下次回调的 preValue
// 	[2,4]	[4, 5, [3, 4]]	return [2,4].concat(flatArr([4, 5, [3, 4]])) 
//  						但是flatArr([4, 5, [3, 4]])是一个执行函数,所以它需要继续执行,这个函数执行完程序才会向下走,所以会执行[4, 5, [3, 4]].reduce()最终会得到[4,5,3,4],
也就是return [2,4].concat([4,5,3,4])作为下次回调的preValue
......
如果每一步的追踪,我大概还有再写20行。如果到这里无法看的明白,你可以尝试去浏览器的控制台打个断点来追踪整个过程,当然我不是很建议你这么做!

​ 我们来打印每次执行后的结果

在这里插入图片描述

​ 来看用箭头函数写的,当然判断数据类型还有更优雅的写法,instanceof,也一起用上

let arr = [2, 4, [4, 5, [3, 4]], 2, 4, 7, 5]
function flatArr(arr) {
  return arr.reduce((preValue, currentValue) => {
    if (currentValue instanceof Array) {
      return preValue.concat(flatArr(currentValue))
    }
    return preValue.concat(currentValue)
  }, [])
}
console.log(flatArr(arr))

​ 好吧,好像也没什么变化,现在要放大招了!JS原生给我们提供了数组降维的方法!

// other1
let arr = [2, 4, [4, 5, [3, 4]], 2, 4, 7, 5]
console.log(arr.flat(Infinity))// flat(num),num来决定给数组降多少维,Infinity就是无限喽
// other2
let arr = [2, 4, [4, 5, [3, 4]], 2, 4, 7, 5]
console.log(arr.toString().split(','))
但是toString().split()有个弊端,它转出来的数组的每一项都是String类型的

4.统计字符串出现次数

​ 有一个字符串 str = ‘# %% %KaTeX parse error: Expected group after '^' at position 1: ^̲%%$%’ ,当然是随便一个字符串,看它里面每个字符出现的次数,统计到一个对象里面。

​ 有朋友要问了,这不是字符串吗,reduce怎么处理字符串!狗拿耗子多管闲事

​ 兄弟咱们别一根筋,我们把str转成数组喽

let str = '#$%%$%$^%$^%$^%'
function objHasKey(obj, value) {
  for (let key in obj) {
    if (value === key) {
      return true
    }
  }
  return false
}
function charSum(str) {
  return str.split('').reduce((pre, cur) => {
    if (objHasKey(pre, cur)) {
      pre[cur]++
    } else {
      pre[cur] = 1
    }
    return pre
  }, {})
}
console.log(charSum(str))

​ charSum(str)函数中,我们先把传入的字符串转为数组,然后遍历它的每一项,将得到的值进行

// 先写一个方法,来判断某个属性是否存在于一个对象objHasKey(obj, value)
// let in 可以遍历出对象中的每一个属性,然后依次跟当前取到的元素来对比
// charSum(str)函数中,我们先把传入的字符串转为数组,然后遍历它的每一项
// 因为属性作为变量无法用obj.key的形式形式添加属性,因为这样key会被识别为字符串
// 所以我们要用obj[key]的形式去添加属性
// str = '#$%%$%$^%$^%$^%'
// pre  			cur	    		return pre
// 	{}				#				#不存在于这个空对象,于是 return {#:1}
//	{#:1}			$				$不存在于这个空对象,于是 return {#:1,$:1}
//	{#:1,$:1}		%				%不存在于这个空对象,于是 return {#:1,$:1,%:1}
//	{#:1,$:1,%:1}	%				%存在于这个空对象,于是 return {#:1,$:1,%:2}
//	.......以下省略
//  最终输出{$: 5, %: 6, ^: 3, #: 1}

​ 好了,有些人可能直接翻到这里了!你没猜错,有原生方法in关键字可以判断一个属性是否存在于一个对象!再改写成箭头函数。

let str = '#$%%$%$^%$^%$^%'
function charSum(str) {
  return str.split('').reduce((pre, cur) => {
    cur in pre ? pre[cur]++ : (pre[cur] = 1)
    return pre
  }, {})
}
console.log(charSum(str))

三、感谢您的阅读

​ 作为个人的第一篇博客,必然会有很多不足之处,如果您发现有什么遗漏或错误之处,还望留言以改正!

​ 如有什么意见或者建议,欢迎留言!

​ 我是coderlie,这是我的第一篇博客,码字不易,转载请注明出处!感谢您的支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值