谈谈reduce的用法

JS 中 Array 常用的三个高阶函数 map,filter,reduce 中,前两个根据字面义就能理解:映射,过滤;而 reduce 单单根据字面义还是想不出其用法的,减少?怎么个减少法?比较容易联想到跟"减少”相关的一个地方可能就是 reduce 的输入是一个数组,其输出是一个单一值的场景,例如下面的求和和求平均数。

  • 求和

    const arr = [13, 23, 24, 50];
    const total = arr.reduce((total, cur) => total + cur);
    
  • 平均值

    const arr = [13, 23, 24, 50];
    const average = arr.reduce((total, cur, index, array) => {
      total += cur;
      if (index === array.length - 1) {
        return total / array.length;
      } else {
        return total;
      }
    });
    

以上是最基础的 reduce 用法。本质上 reduce 是一种循环的实现,这意味着其实可以用 reduce 来实现 map 和 filter,因为这二者也是循环。举例之前先稍微解释下 reduce 的参数列表。reduce 接收四个参数:initial(初始值)、cur(当前值)、当前值索引(index)、数组本身(arr)。这里 initial 有时被称为 pre(previous),相较于后边的 cur(current)。如果不传初始值给 initial,initial 自动取数组的第一项,这点在 initial 的类型和数组项类型不一致时尤其需要注意。事实上 reduce 运算开始执行循环时取数组的第一项,赋给 cur,然后每次向右步进一,把每步计算结果更新到 initial 上,循环完整个数组后返回 initial。

  • 用 reduce 实现 map

    const arr = [13, 23, 24, 50];
    const doubleArr = arr.reduce((res, cur) => {
      res.push(cur * 2);
      return res;
    }, []);
    
  • 用 reduce 实现 filter

    const arr = [13, 23, 24, 50];
    const singles = arr.reduce((total, cur) => {
      if (cur % 2 === 1) total.push(cur);
      return total;
    }, []);
    
  • 用 reduce 替代组合使用 map 和 filter 的场景

    const persons = [
      { name: "Alice", sex: "female" },
      { name: "Bob", sex: "male" },
      { name: "Claire", sex: "female" },
      { name: "Dave", sex: "male" }
    ];
    

    用 filter 和 map 得到可以勾搭的男孩:

    const goodBoys = persons.filter(person => person.sex === "male").map(person => {
      return {
        ...person,
        available: true
      };
    });
    

    用 reduce 只用一次循环实现相同的效果:

    const goodBoys = persons.reduce((initial, cur, index, arr) => {
      if (arr[index].sex === "male") {
        initial.push({
          ...arr[index],
          available: true
        });
      }
    
      return initial;
    }, []);
    
  • 用 reduce 实现计数

    const fruitBasket = [
      "banana",
      "cherry",
      "orange",
      "apple",
      "cherry",
      "orange",
      "apple",
      "banana",
      "cherry",
      "orange",
      "fig"
    ];
    const count = fruitBasket.reduce((tally, fruit) => {
      tally[fruit] = (tally[fruit] || 0) + 1;
      return tally;
    }, {});
    count; // { banana: 2, cherry: 3, orange: 3, apple: 2, fig: 1 }
    
  • 用 reduce 将一个 2 维数组扁平化

    const data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
    const flat = data.reduce((total, amount) => {
      return total.concat(amount);
    }, []);
    flat; // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
    
  • 用 reduce 甚至可以实现一个类似函数式编程中常用的 pipeline:

    const increment = input => input + 1;
    const decrement = input => input - 1;
    const double = input => input * 2;
    const halve = input => input / 2;
    
    const pipeline = [increment, double, decrement, halve];
    const result = pipeline.reduce((total, func) => func(total), 10); // 10.5
    

    pipeline 的好处是当需要变更流程时,只需要更新 pipeline 数组,剩下的事 pipeline 会搞定。比如将 pipeline 改成

    pipeline = [increment, havle, double, double, decrement, halve, increment];
    

    如果采用一次调用函数的方式,更改起来就比较繁琐。

参考

How JavaScript’s Reduce method works, when to use it, and some of the cool things it can do