可组合性

考虑一下,我们怎样才可以在不使用高阶函数的情况下,编写以上示例(找到最大的脚本)?代码没有那么糟糕。

  1. let biggest = null;
  2. for (let script of SCRIPTS) {
  3. if (biggest == null ||
  4. characterCount(biggest) < characterCount(script)) {
  5. biggest = script;
  6. }
  7. }
  8. console.log(biggest);
  9. // → {name: "Han", …}

这段代码中多了一些绑定,虽然多了两行代码,但代码逻辑还是很容易让人理解的。

当你需要组合操作时,高阶函数的价值就突显出来了。举个例子,我们编写一段代码,找出数据集中男人和女人的平均年龄。

  1. function average(array) {
  2. return array.reduce((a, b) => a + b) / array.length;
  3. }
  4. console.log(Math.round(average(
  5. SCRIPTS.filter(s => s.living).map(s => s.year))));
  6. // → 1185
  7. console.log(Math.round(average(
  8. SCRIPTS.filter(s => !s.living).map(s => s.year))));
  9. // → 209

因此,Unicode 中的死亡脚本,平均比活动脚本更老。 这不是一个非常有意义或令人惊讶的统计数据。 但是我希望你会同意,用于计算它的代码不难阅读。 你可以把它看作是一个流水线:我们从所有脚本开始,过滤出活动的(或死亡的)脚本,从这些脚本中抽出时间,对它们进行平均,然后对结果进行四舍五入。

你当然也可以把这个计算写成一个大循环。

  1. let total = 0, count = 0;
  2. for (let script of SCRIPTS) {
  3. if (script.living) {
  4. total += script.year;
  5. count += 1;
  6. }
  7. }
  8. console.log(Math.round(total / count));
  9. // → 1185

但很难看到正在计算什么以及如何计算。 而且由于中间结果并不表示为一致的值,因此将“平均值”之类的东西提取到单独的函数中,需要更多的工作。

就计算机实际在做什么而言,这两种方法也是完全不同的。 第一个在运行filtermap的时候会建立新的数组,而第二个只会计算一些数字,从而减少工作量。 你通常可以采用可读的方法,但是如果你正在处理巨大的数组,并且多次执行这些操作,那么抽象风格的加速就是值得的。