很多人始终不明白,ES6 数组的遍历方法最重要的不在代码本身,而是语义化!map、reduce、filter、some、every、find ...

2,487 阅读5分钟

前言

不知不觉,ES6 的数组新语法已经出了好多年了,大家都应该理解一句话: ES6 数组方法带来的最大优势是语义化

很多人在使用 ES6 数组语法时,将它当作了便捷操作。

甚至更多千奇百怪的用法,我希望大家在看完该文章后,去检查一下自己的项目中,这些语法是否可以一眼让大家看出这些方法是做什么用的。

反面操作

这里简单列举一些反面操作:

  • map 作为循环使用,通过 map 计算总数
const arr = [{..., count: 1}, {..., count: 5}]

let total = 0;
arr.map(v => {
    total += v.count
})
  • map 作为循环使用,改变数组中的对象的属性
// 操作1
let arr = [{..., id: '1'}, {..., id '2'}]
arr = arr.map(v => ({...v, id: Number(id)}))

// 操作2
let arr = [{..., id: '1'}, {..., id '2'}]
arr.map(v => {
    v.id = Number(v.id)
})

其中尤其是改变对象属性中的操作1,是误用最多的用法。

讲解

我一般不做基本语法讲解,因为罗列式的讲解让你左脑进右脑出,不带给大家任何价值。面向实际的使用才能让大家影响深刻。

接下来,我将假设自己是一个快递站的老板,我作为一个快递站的老板,应该如何通过这些语法来处理我的快递。

为了让代码更清晰,我将以 ed 来作为快递的简称(express delivery)

map

语义 :将一些东西转换为另一些东西。

正面示例:
我需要将所有的 快递单号 + 派送时间 整理出来,来决定我的送货顺序。

const edArr = [{..., id: '666666666', date: '2024-02-23'}, {..., id: '666666688', date: '2024-02-22'}, ...]

const data = edArr.map(v => ({id: v.id, date: v.date}))

反面示例:
我希望给所有快递贴一个广告。

let edArr = [{..., id: '666666666', date: '2024-02-23'}, {..., id: '666666688', date: '2024-02-22'}, ...]

edArr = edArr.map(v => ({...v, ad: '要问快递哪家强,就来掘金找sincenir!'}))

虽然反面案例也能实现,并且也没有代码量的提升,但是我强烈不建议这么干。这会大大降低你代码的可阅读性。

foreach

语义:我希望循环做某些事。

显然,相较于 map ,上面的反面案例应该交由 foreach 操作,而正面案例则是 foreach 的反面案例。这两者大家常常是反着用的。

正面示例:
我希望给所有快递贴一个广告。

let edArr = [{..., id: '666666666', date: '2024-02-23'}, {..., id: '666666688', date: '2024-02-22'}, ...]
edArr.foreach(v => { v.ad = '要问快递哪家强,就来掘金找sincenir!' })

反面示例:
我需要将所有的 快递单号 + 派送时间 整理出来,来决定我的送货顺序。

const edArr = [{..., id: '666666666', date: '2024-02-23'}, {..., id: '666666688', date: '2024-02-22'}, ...]

const data = []
edArr.foreach(v => {
    data.push({id: v.id, date: v.date})
})

filter

语义:我希望筛选出来一些东西。

正面示例:
我希望找到所有用户拒收的快递,发回给快递公司。

const edArr = [{..., reject: false}, {..., reject: true}, ...]
const rejectEdArr = edArr.filter(v => v.reject === true)

反面示例:
我希望找到单号为 8888 的快递。

const edArr = [{..., id: '6666'}, {..., id: '8888'}, ...]
const nArr = edArr.filter(v => v.id === '8888')

显而易见的是,快递单号是唯一的,用 filter 在这里是不合适的。
因为它最多筛选出一个符合条件的快递,且这个一是已知的。

find

语义:找一个符合条件的给我。

正面示例:
快递员:我车装差不多了,再给我一个 270号楼 的快递,我一块儿送过去。

const edArr = [{..., address: '271'}, {..., address: '270'}, ...]
const oneEd = edArr.find(v => v.address === '270')

反面示例:
顾客:今天有没有我的快递?

const edArr = [{..., user: 'zhangsan'}, {..., user: 'sincenir'}, ...]
const ed = edArr.find(v => v.user === 'sincenir')

if (ed) {
    // 有
} else {
    // 没有
}

findIndex

语义:找符合条件的是第几个。

正面示例:
快递员:我今天还需要送多少快递?

const edArr = [{..., date: '2024-02-23'}, {..., date: '2024-02-24'}, ...]
const count = edArr.findIndex(v => v.date !== '2024-02-23') + 1

反面示例:
顾客:今天有没有我的快递?

const edArr = [{..., user: 'zhangsan'}, {..., user: 'sincenir'}, ...]
const haveSincenir = edArr.find(v => v.user === 'sincenir') > -1

这里需要提醒大家,findIndex 是滥用极其严重的函数,包括有没有很多人都喜欢用 findIndex 去做,这些都是不对的。
当你不知道你是否要使用 findIndex 时,据我观察,大多数时候都应该使用别的语法。

some

语义:这里有没有符合条件的。

正面示例:
顾客:这里有没有我的快递?

const edArr = [{..., user: 'zhangsan'}, {..., user: 'sincenir'}, ...]
const haveSincenir = edArr.some(v => v.user === 'sincenir')

every

语义:这里是否全符合条件。

正面示例:
快递员:今天还有没有需要送的快递啦?

const edArr = [{..., date: '2024-02-23'}, {..., date: '2024-02-24'}, ...]
const haveToday = edArr.every(v => v.date !== '2024-02-23')

reduce

语义:基于这些帮我算一个东西。

正面示例:
咱们今天收的快递总共多少钱啊?我算算账对不对。

const edArr = [{..., amount: '13'}, {..., amount: '20'}, ...]
const totalAmount = edArr.reduce((r, v) => (r + v.amount), 0)

最后

语义化最重要的点在于,我们看到代码的第一眼,不需要看细节就能明白我们究竟准备做什么。
而不是为了便捷去使用这些方法,否则你的代码依旧是面条形状的面向过程编程。

最后我们总结下所有语法的语义:

  • map: 将一些东西转换为另一些东西
  • foreach: 我希望循环做某些事
  • filter: 我希望筛选出来一些东西
  • find: 找一个符合条件的给我
  • findIndex: 找符合条件的是第几个
  • some: 这里有没有符合条件的
  • every: 这里是否全符合条件
  • reduce: 基于这些帮我算一个东西