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 实现 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" }
      ];
      const goodBoys = persons.filter(person => person.sex === "male").map(person => {
        return {
          ...person,
          available: true
        };
      });
      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 = [increment, havle, double, double, decrement, halve, increment];

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

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

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

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

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

参考

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