你不知道的reduce的用法

·  阅读 1203
你不知道的reduce的用法

这是我参与新手入门的第3篇文章

一、前言

reduce 是我最喜欢的 JavaScript 数组对象中的高阶函数之一。 reduce 很强大,基本所有高阶函数都可以通过reduce来实现而且可以让我们优雅的编写JavaScript~

但是reduce也是一个令人“难以理解”的方法,现在我们一步步加深reduce的理解吧。

二、reduce-语法

让我们看看MDN是怎么介绍 reduce 的呢?

image.png

image.png

相信很多小伙伴刚看到这个简介都是一脸懵逼的,现在我们来用白话文的方式解读一下Array.reduce方法吧

// 最简单的reduce例子: 数组求和
const array = [1, 2, 3, 4];
const sum = array.reduce((accumulator, currentValue) => {
    return accumulator + currentValue
})
console.log(sum) // 数组的总和是:10
复制代码

学习一个API前,我们需要API的功能和作用。也就是它是干什么的,他能带来什么作用,正所谓知己知彼百战百胜~

学习一个陌生的API基本上遵循以下步骤:

  1. 找到相关文档、阅读API的简介
  2. API的语法:参数定义、返回值、api结构。
  3. 按照示例编写代码测试与体验

Array.reduce

简介:Array.reduce() 接收一个函数作为累加器 数组中的每个值(从左到右)开始缩减,最终返回函数、

语法:

array.reduce(function callback(accumulator, currentValue, index, array) {
  // 返回执行某逻辑的结果,做为下一个accumulator的值
  // 返回值可以是:object、array、string、number等等..reduce灵活的根本!!
}, [initialValue])
复制代码

此方法接受两个参数:

  • callback(...) (必须)(接受4个参数:)
    • accumulator (初始值、或者通过计算结束后的返回值)
    • currentValue (当前元素值)
    • currentIndex (当前元素索引)
    • sourceArray (源数组)。
  • initialValue (可选)。
    • 如果不传默认值,第一次执行callback函数的时候会从数组索引1的地方开始、accumulator的值为数组的第一个值
    • 如果传了,则从数组索引0开始、accumulato的值为默认的值
    • 如果没有默认值、且数组是空的、则会报错!!
    • 初始化的值建议还是要传递、reduce灵活变化的根本

reduce注意事项

  • reduce() 对于空数组是不会执行回调函数
  • reduce() 如果在没有初始值的空数组上执行则会报错!!!
  • reduce() 不会改变原数组

简单的动画理解 reduce

  1. reduce 会对数组每一个元素都执行我们编写的函数
  2. 函数 return 的值做为下一个函数 accumulator 的值, 就是输入的值。
  3. 执行顺序是升序执行,从左到右

1n9l6-y5ti5.gif

三、见识一下 reduce 的强大

通过一个简单的案例让我们见识一下 reduce 的强大吧!!!

题目: 数组对象: 去重id,去除空评论,返回数据用户ID数组

let user = [
  {id: 1, content: '哈哈哈'},
  {id: 2, content: ''},
  {id: 2, content: ''},
  {id: 3, content: '嘿嘿嘿'},
  {id: 3, content: '嘿嘿嘿'},
  {id: 3, content: '嘿嘿嘿'},
  {id: 4, content: '滴滴滴'},
  {id: 5, content: '呃呃呃'},
  {id: 6, content: '哒哒哒'},
  {id: 7, content: '咚咚咚'},
]
复制代码

常见做法:

// 简单~ 这样不是挺香吗?:(ps:只是举例、还有很多其他方式~)
Array.from(new Set(user.filter(e => e.content).map(e => e.id)))
// [1, 3, 4, 5, 6, 7]
复制代码

但是!!!你有没有想过这些语法糖是够简洁了但是、遍历了多少次呢、语义化是不是够明显呢?能不能用别的方法一次性就解决呢

使用 reduce

user.reduce((accumulator, value) => {
    const { id, content } = value 
    const isPush = id && !accumulator.includes(id) && content
    return isPush ? [...accumulator, value.id] : accumulator
}, [])
// [1, 3, 4, 5, 6, 7]
// 是不是不用 map filter from Set...这些操作了呢?
复制代码

拓展知识:什么?用了...拓展运算符遍历多了,很多余?

使用逗号运算符

  • 逗号运算符是二元运算符,它能够先执行运算符左侧的操作数,然后再执行右侧的操作数,最后返回右侧操作数的值。
user.reduce((accumulator, value) => {
  const { id, content } = value 
  const isPush = id && !accumulator.includes(id) && content
  return isPush ? (accumulator.push(value.id), accumulator) : accumulator
}, [])
复制代码

四、万物皆可reduce

1. reduce 实现 map 方法

先让我们看看map方法实现了什么功能: map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

const arr = [{name:'Amy', age: 10}, {name:'Bob', age: 20}]

// map用法
arr.map(itme => itme.name) // ["Amy", "Bob"]

// 利用扩展运算符 与默认参数
arr.reduce((total, value) => [...total, value.name], [])
// ["Amy", "Bob"] 
复制代码

接下来我们看看是怎么实现的

  1. 我们先给默认值给的是一个空数组,因为map方法返回值是数组。
  2. [...total, value.name] 我们将name拿出来放入数组中,return的值会传入下个函数的total中,此时total为:['Amy']。
  3. 函数继续执行... 最后返回 累加完成的数组 ["Amy", "Bob"]
  4. ps:total其实就是上文的accumulator,如果这个不太熟悉的建议会回头看看语法,再按照案例编写代码加深体会。
  5. 上面方法是使用了 ES6的箭头函数展开运算符进行简写操作

2. reduce 实现 filter 方法

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素

const arr = [{name:'Amy'}, {name:'Bob'}]

// filter用法
arr.filter(itme => itme.name === 'Amy')
// [{name: "Amy"}]

// 利用三元表达式、如果是真扩展的方式返回、如果是假不处理返回原来的值
arr.reduce((total, value) => value.name === 'Amy' ? [...total, value] : total, [])
// [{name: "Amy"}]
复制代码

让我们看看怎么实现:

  1. 我们先给默认值给的是一个空数组,因为filter方法返回值是数组。
  2. 我们利用reduce return值是下一个函数的total值的特性。我们用三元表达式判断, 如果表达式为真则 追加入数组中传递下去。如果为假则 返回为改动total数组。
  3. 巧妙的运用 callback函数与返回值则是reduce强大的地方。

3.reduce 替代 map + filter

const arr = [{name:'Amy', age: '18'}, {name:'LaLa', age: '18'}, {name:'Bob', age: '22'}]

// filter + map 用法
arr.filter(item => item.age === '18').map(item => item.name)

// 利用扩展运算符 与默认参数
arr.reduce((total, value) => value.age === '18' ? [...total, value.name] : total, [])
复制代码

让我们看看是怎么实现的:

  1. 和上面的一样定义了了默认值、通过return 的值的处理,实现数据的处理。
  2. 慢慢你就会发现其实套路都是一样的、首先你要明白你需要的是什么(返回值,初始值、你想传递给下一个函数的内容)

4.reduce实现 some 方法

首先我们要知道方法的用途、才能使用我们的方法去改写。

some() 方法:当有一个条件满足就返回 true

const arr = [{name:'Amy', age:18}, {name:'Bob', age:20}]

// some 当有一个满足条件就是true
arr.reduce((total, value) => total || value.age > 18, false)
复制代码

让我们看看是怎么实现的:

  1. 我们要审题:“当有一个条件满足就返回 true ”
  2. 我们在return的 时候使用 "||" 短路运算符的特性
  3. 所以我们默认值要设置为 false,当满足一个条件的时候total则返回true,利用短路运算符的特性则可以一直返回true了。

5.reduce实现 every方法

every() 方法:当所有的条件都满足的时候才返回true

const arr = [{name:'Amy', age:18}, {name:'Bob', age:20}]

// every 当所有的条件都满足的时候才返回true
arr.reduce((total, value) => total && value.age > 18, true)
复制代码

让我们看看是怎么实现的:

  1. 其实这里的逻辑和上面 some 的一样的、我们依据 every 方法的特性加上 “&&” 与逻辑符。使每一个元素都要判断、当有一个条件不满足的时候、total变成了false。在“&&”中则永远都是false

6. reduce 实现 flat 方法

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

// 简化版:
var arr = [1, 2, 3, 4, [1, 2, 3, 4, 5, [2, 3, 4]]]

function arrFlat(data) {
  return data.reduce((total, value) => {
    return Array.isArray(value) ? [...total, ...arrFlat(value)] : [...total, value]
  }, [])
}
arrFlat(arr)
// (12) [1, 2, 3, 4, 1, 2, 3, 4, 5, 2, 3, 4]
复制代码
// 完整版
var arr = [1, 2, 3, 4, [1, 2, 3, 4, 5, [2, 3, 4]]]

function arrFlat(arr, depth = 1) {
  return depth 
    ? arr.reduce((total, currentValue) => {
      return Array.isArray(currentValue) ? [...total, ...arrFlat(currentValue, depth - 1)] : [...total, currentValue]
    }, []) 
    : arr
}

arrFlat(arr, 2)
// (12) [1, 2, 3, 4, 1, 2, 3, 4, 5, 2, 3, 4]
复制代码

让我们看看是怎么实现的:

  1. 这里利用了递归arrFlat函数
  2. 判断 depth 数量,默认是1,如果depth 为 0的时候返回元素,如果不为0的时候则代表还有更深入的层次。进行深层次的 reduce 遍历,depth 很重要可以控制层次
  3. 解构也很重要~~

7. reduce 实现数组的去重

const arr = ['a', 'a', 'b', 'b', 'c', 'c', 'd']

arr.reduce((total, value) =>  total.includes(value) ? total : [...total, value], [])
// (4) ["a", "b", "c", "d"]
复制代码

让我们看看是怎么实现的:

  1. 我们利用了 includes 判断 total 总数组中有没有存在的数组如果有则返回原数组,如果没有则新增数组。

五、总结

关于reduce的方法真的太多了~ 很难一一介绍完毕、但是通过这些经典的案例我相信大家已经明白了 reduce 的强大。

万物皆可 reduce,可以这样做但是没必要。

本文只是想介绍 reduce 的强大,是否应该在每种情况下都要使用 reduce?没有最好的方法、只有最适合你的方法。

reduce虽然它可能看起来让人头脑发晕“难以理解“,但学习它只会对你有利。reduce() 方法很强大,它的是能够在 callbakc 回调函数中编写不同的方法,并为数组的每个元素提供操作空间。

分类:
前端
分类:
前端