JavaScript 数组 reduce 总是不会用?看看这 5 个例子就懂了!

开发 前端
相信不少初学者曾经都被 JavaScript 数组的 reduce方法困扰过,一直搞不明白到底怎么用。reduce方法是按顺序对数组每个元素执行某个函数,这个函数接收上一次执行结果作为参数,并将结果传给下一次调用。

[[355931]]

相信不少初学者曾经都被 JavaScript 数组的 reduce方法困扰过,一直搞不明白到底怎么用。reduce方法是按顺序对数组每个元素执行某个函数,这个函数接收上一次执行结果作为参数,并将结果传给下一次调用。reduce方法用得好可以简化复杂的逻辑,提高代码可读性。通过下面几个例子可以帮你快速理解reduce的用法。

1.数字数组求和

这是reduce最常见的入门级例子。如果用传统的for循环是这样的:

 

  1. function sum(arr) { 
  2.   let sum = 0; 
  3.   for (const val of arr) { 
  4.     sum += val; 
  5.   } 
  6.   return sum
  7.  
  8. sum([1, 3, 5, 7]); // 16 

如果用 reduce():

 

  1. function sum(arr) { 
  2.   const reducer = (sum, val) => sum + val; 
  3.   const initialValue = 0; 
  4.   return arr.reduce(reducer, initialValue); 
  5.  
  6. sum([1, 3, 5, 7]); // 16 

reduce()函数的第一个参数是一个reducer函数,第二个参数是初始值。在每个数组元素上执行reducer函数,第一个参数是“累进值”。累进值的初始值是initialValue,并且在每一轮调用后更新为reducer函数的返回值。

为了帮助理解,可以用for循环实现一个简单的reduce()函数:

 

  1. function reduce(arr, reducer, initialValue) { 
  2.   let accumulator = initialValue; 
  3.   for (const val of array) { 
  4.     accumulator = reducer(accumulator, val); 
  5.   } 
  6.   return accumulator; 

2.对象数组数字属性值求和

单看 reduce() 本身,大家更多的感受是它的晦涩难懂,而不是有多好用。如果仅仅是为了给数字数组求和,用for循环可能来得更直观。但是,当你把它跟其他数组方法(比如filter和map)结合使用时,才能感受到它的强大和方便。

举个栗子,假设有个对象数组,每个对象都有个total属性。对这些total求和:

 

  1. const lineItems = [ 
  2.   { description: 'Eggs (Dozen)', quantity: 1, price: 3, total: 3 }, 
  3.   { description: 'Cheese', quantity: 0.5, price: 5, total: 2.5 }, 
  4.   { description: 'Butter', quantity: 2, price: 6, total: 12 } 
  5. ]; 

用reduce可以这样写:

  1. lineItems.reduce((sum, li) => sum + li.total, 0); // 17.5 

这样是能得到最终结果,但是代码的可组合性就没那么好,可以做些优化,把total属性提前提取出来:

  1. lineItems.map(li => li.total).reduce((sum, val) => sum + val, 0); 

第二种方式为什么更好?因为这样就可以把求和的逻辑抽象到一个单独的函数sum()中,方便以后重用。

 

  1. //汇总 total  
  2. lineItems.map(li => li.total).reduce(sumReducer, 0); 
  3.  
  4. //  汇总 quantity 
  5. lineItems.map(li => li.quantity).reduce(sumReducer, 0); 
  6.  
  7. function sumReducer(sum, val) { 
  8.   return sum + val; 

这种抽象比较重要,因为你觉得 sumReducer() 逻辑不会变,其实不然。比如,这个求和逻辑并没有考虑到0.1 + 0.2 !== 0.3的问题。(参考 为什么 0.1 + 0.2 = 0.300000004)如果有了这个抽象,要修复这个缺陷就比较方便了。比如:

 

  1. const { round } = require('lodash'); 
  2.  
  3. function sumReducer(sum, val) { 
  4.   // 保留2位小数 
  5.   return _.round(sum + val, 2); 

3.求最大值

reduce()通常用来求和,但它的功能远不止这个。累进值accumulator 可以是任意值:数字,null,undefined,数组,对象等。

举个栗子,假设有个日期数组,要找出最近的一个日期:

 

  1. const dates = [ 
  2.   '2019/06/01'
  3.   '2018/06/01'
  4.   '2020/09/01', // 这个是最近日期,但是怎么找到它? 
  5.   '2018/09/01' 
  6. ].map(v => new Date(v)); 

一种方法是给数组排序,找最后一个值。看上去可行,但是效率没那么高,并且用数组的默认方法给日期对象排序其实是有点小问题的。不只是日期对象,任何类型的值在sort()方法中比较,都会默认先转成字符串比较,最终结果可能并不是你想要的。

 

  1. const a = [4,1,13,2]; 
  2. // 惊不惊喜,意不意外? 
  3. a.sort(); // [1, 13, 2, 4] 

这里就可以用reduce()来处理:

 

  1. // 这里是用 `>` 和`<` 比较日期对象,因此不会有问题 
  2. const maxDate = dates.reduce((max, d) => d > max ? d : max, dates[0]); 

4.分组计数

假设有个对象数组,每个对象上有个age属性:

 

  1. const characters = [ 
  2.   { name'Tom', age: 59 }, 
  3.   { name'Jack', age: 29 }, 
  4.   { name'Bruce', age: 29 } 
  5. ]; 

怎样返回一个对象,包含每个age的人物角色数量?比如这样: { 29: 2, 59: 1 }.

用 reduce() 的实现方式如下:

 

  1. const reducer = (map, val) => { 
  2.   map[val] = map[val] || 1; 
  3.   ++map[val]; 
  4.   return map; 
  5. }; 
  6. characters.map(char => char.age).reduce(reducer, {}); 

5.Promise 动态链式调用

假设你有一个异步函数数组,想要按顺序执行:

 

  1. const functions = [ 
  2.   async function() { return 1; }, 
  3.   async function() { return 2; }, 
  4.   async function() { return 3; } 
  5. ]; 

如果是静态的Promise代码,我们直接在代码里链式调用就行了。但如果是动态的Promise数组,可以用reduce串起来:

 

  1. // 最后 `res`的结果等价于`Promise.resolve().then(fn1).then(fn2).then(fn3)` 
  2. const res = await functions. 
  3.   reduce((promise, fn) => promise.then(fn), Promise.resolve()); 
  4. res; // 3 

当然,reduce 能做的事情还有很多,它本质上是对数组元素执行某种“累进”操作,最终返回单个值。

本文转载自微信公众号「1024译站」,可以通过以下二维码关注。转载本文请联系1024译站公众号。

 

责任编辑:武晓燕 来源: 1024译站
相关推荐

2021-03-16 15:12:57

CompletableFuture机制java

2024-08-12 12:25:25

SpringMVC开发

2018-09-13 10:40:40

Linux命令find

2020-05-14 08:59:28

API网关性能

2024-09-09 08:36:36

Java操作远程服务器

2020-09-01 14:17:03

WindowsDefender微软

2020-12-18 09:45:33

DockerLinux命令

2022-02-22 08:25:51

typeScript泛型概念泛型使用

2020-11-09 09:03:35

高并发多线程ThreadLocal

2020-09-27 06:50:56

Java互联网注解

2021-07-13 12:21:34

PythonRPC通信

2012-05-02 15:38:49

金山快盘网盘

2019-04-17 09:01:17

2021-03-22 08:20:48

5G工业互联网

2020-10-21 10:02:16

架构运维技术

2019-09-03 09:30:46

ss 命令SocketLinux

2021-11-10 07:47:48

Traefik边缘网关

2022-08-12 15:58:34

Docker

2019-11-28 16:48:00

华为Mate X

2017-09-19 19:07:00

ZStack混合云灾备
点赞
收藏

51CTO技术栈公众号