李子默

Bugslayer

谈谈reduce的用法

2018-11-08 Azrieljavascript

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