Array.prototype.reduce() 详解

本文转载自我的个人博客

这是一篇译文,有兴趣的同学可以阅读官方文档,图省事的同学可以直接看这篇精简版的译文。

reduce()是一个数组方法,它接受一个由你定义回调函数,在数组的每一项上都会执行这个回调函数,最终返回一个值。

我们来先看两个例子感受一下:

  const array1 = [1, 2, 3, 4];
  //可以自己定义的回调函数
  const reducer = (accumulator, currentValue) => accumulator + currentValue;

  // 例子一
  console.log(array1.reduce(reducer));
  // 1 + 2 + 3 + 4
  // 输出: 10

  // 例子二
  console.log(array1.reduce(reducer, 5));
  // 5 + 1 + 2 + 3 + 4
  // 输出: 15

语法

  arr.reduce(callback[, initialValue])

reduce()方法本身接受两个参数,callback(回调函数),和initialValue(初始值)。而回调函数本身又可以四个参数,所以整体的参数表如下所示:

  • callback
    回调函数,数组中的每一项元素都将被执行此回调函数,接受四个参数:
    • accumulator
      存放的是每一次调用回调函数的返回值
    • currentValue
      当前正在处理的数组元素
    • currentIndex(可选)
      当前正在处理的数组元素的index
    • array(可选)
      调用reduce()的数组
  • initialValue(可选)
    如果提供了initialValue,那么它将作为第一次调用回调函数时的第一个参数,如果没有提供,数组中的第一个元素将会被作为initialValue。如果调用reduce()的数组是一个空数组,又没有提供initialValue,程序将会报错。

举两个例子,我们来看一下在有和没有initialValue的时候,每执行一次回调函数,各参数的变化:

例一,没有定义 initialValue 的情况

  const array1 = [0, 1, 2, 3, 4];
  const reducer = (accumulator, currentValue) => accumulator + currentValue;
  console.log(array1.reduce(reducer));
callback accumulator currentValue currentIndex array return value
first call 0 1 1 [0, 1, 2, 3, 4] 1
second call 1 2 2 [0, 1, 2, 3, 4] 3
third call 3 3 3 [0, 1, 2, 3, 4] 6
fourth call 6 4 4 [0, 1, 2, 3, 4] 10

由于没有定义initialValue,在第一次执行累加时就需要数组中的第一项和第二项,第一项的值赋给了accumulator,真正的累加从第二项开始。所以虽然数组中有五项,但是回调函数一共只被执行了四次。

例二,定义了initialValue 的情况

  const array1 = [0, 1, 2, 3, 4];
  const reducer = (accumulator, currentValue) => accumulator + currentValue;

  //initialValue = 10
  console.log(array1.reduce(reducer, 10));

callback accumulator currentValue currentIndex array return value
first call 10 0 0 [0, 1, 2, 3, 4] 10
second call 10 1 1 [0, 1, 2, 3, 4] 11
third call 11 2 2 [0, 1, 2, 3, 4] 13
fourth call 13 3 3 [0, 1, 2, 3, 4] 16
fifth call 16 4 4 [0, 1, 2, 3, 4] 20

在回调函数第一次执行时,initialValue的值被赋给accumulator,累加从数组的第一项开始执行,所以回调函数一共被执行了五次。

注意事项

如果调用reduce()的数组是一个空数组,又没有提供initialValue,程序将会报错。

提供initialValue是一个更安全的做法。在某些情况下,如果不提供initialValue,输出将具有不确定性,比如下面这个例子:

  var maxCallback = ( acc, cur ) => Math.max( acc.x, cur.x );
  var maxCallback2 = ( max, cur ) => Math.max( max, cur );

  // 没有提供 initialValue
  [ { x: 22 }, { x: 42 } ].reduce( maxCallback ); // 42
  [ { x: 22 }            ].reduce( maxCallback ); // { x: 22 }
  [                      ].reduce( maxCallback ); // TypeError

  // 提供了initialValue 
  // map 与 reduce 的合用,这是一个更安全的策略,这样即使是空数组也不会报错
  [ { x: 22 }, { x: 42 } ].map( el => el.x )
                          .reduce( maxCallback2, -Infinity );

更多的例子

reduce()可以用在很多地方:

数组求和:

  var total = [ 0, 1, 2, 3 ].reduce(
    ( accumulator, currentValue ) => accumulator + currentValue,
    0
  );

对象的属性求和:

  var initialValue = 0;
  var sum = [{x: 1}, {x: 2}, {x: 3}].reduce(
      (accumulator, currentValue) => accumulator + currentValue.x
      ,initialValue
  );

  console.log(sum) // logs 6

合并数组:

  var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
    ( accumulator, currentValue ) => accumulator.concat(currentValue),
    []
  );

计算对象中实例的个数:

  var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

  var countedNames = names.reduce(function (allNames, name) { 
    if (name in allNames) {
      allNames[name]++;
    }
    else {
      allNames[name] = 1;
    }
    return allNames;
  }, {});
  // countedNames is:
  // { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

根据对象属性分组:

  var people = [
    { name: 'Alice', age: 21 },
    { name: 'Max', age: 20 },
    { name: 'Jane', age: 20 }
  ];

  function groupBy(objectArray, property) {
    return objectArray.reduce(function (acc, obj) {
      var key = obj[property];
      if (!acc[key]) {
        acc[key] = [];
      }
      acc[key].push(obj);
      return acc;
    }, {});
  }

  var groupedPeople = groupBy(people, 'age');
  // groupedPeople is:
  // { 
  //   20: [
  //     { name: 'Max', age: 20 }, 
  //     { name: 'Jane', age: 20 }
  //   ], 
  //   21: [{ name: 'Alice', age: 21 }] 
  // }

官方文档里还有很多更高级的用法,传送门

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容