数组reduce分析

435 阅读2分钟

以前总是习惯用forEach来遍历数组,看别人的代码总是使用reduce,所以学习一下reduce的大概用法。

reduce通过一个传入的函数,对数组的每一项从左往右进行累加,最终产生一个结果。

例如,把数组里1-100的元素相加

```javascript

    // 初始化1-100的数组
    let numbers = [...new Array(100).keys()];
    // 计算数组元素之和
    let sum = numbers.reduce((sum, cur) => sum + cur);
    console.log(sum, "sum");// 4950

```

reduce使用语法

arr.reduce(callback(previousValue, currentValue[, index[, array]])[, initialValue])

reduce接收一个回调函数和一个初始值,它对数组的每一项进行处理,最后一次的回调函数返回值作为reduce的返回值。回调函数的参数如下:

  • previousValue是回调函数上一次的返回值。第一次执行的时候如果传入initialValue,则previousValue为initialValue的值,如果没有传入,则选择数组的第一个元素。(这里是数组的第一个元素,具体看下面的源码分析)

  • currentValue是数组遍历中正在处理的元素

  • index是currentValue 在数组中的索引。

  • array是调用reduce的数组。

reduce 源码解析

源码来自于mdn的Polyfill

function(callback /*, initialValue*/) {
      if (this === null) {
        throw new TypeError( 'Array.prototype.reduce ' +
          'called on null or undefined' );
      }
      if (typeof callback !== 'function') {
        throw new TypeError( callback +
          ' is not a function');
      }

      // 1. Let O be ? ToObject(this value).
      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // Steps 3, 4, 5, 6, 7
      var k = 0;
      var value;

      if (arguments.length >= 2) {
        value = arguments[1];
      } else {
        while (k < len && !(k in o)) {
          k++;
        }

        // 3. If len is 0 and initialValue is not present,
        //    throw a TypeError exception.
        if (k >= len) {
          throw new TypeError( 'Reduce of empty array ' +
            'with no initial value' );
        }
        value = o[k++];
      }

      // 8. Repeat, while k < len
      while (k < len) {
        // a. Let Pk be ! ToString(k).
        // b. Let kPresent be ? HasProperty(O, Pk).
        // c. If kPresent is true, then
        //    i.  Let kValue be ? Get(O, Pk).
        //    ii. Let accumulator be ? Call(
        //          callbackfn, undefined,
        //          « accumulator, kValue, k, O »).
        if (k in o) {
          value = callback(value, o[k], k, o);
        }

        // d. Increase k by 1.
        k++;
      }

      // 9. Return accumulator.
      return value;
    }

首先是参数校验。对调用reduce的数组进行非null/undefined判断,这里应该使用==,再对传入的callback进行函数类型的校验。

然后是对第一次调用时的previousValue和index的处理.

if (arguments.length >= 2) {
        value = arguments[1];
      } else {
        while (k < len && !(k in o)) {
          k++;
        }

        // 3. If len is 0 and initialValue is not present,
        //    throw a TypeError exception.
        if (k >= len) {
          throw new TypeError( 'Reduce of empty array ' +
            'with no initial value' );
        }
        value = o[k++];
      }
  1.  如果传入参数里传入了initialValue,则参数长度满足大于等于2(没有写==2,很细节),将initialValue赋值给previousValue,index为0

  2. 如果没有传入initialValue那么获取数组里的第一个元素,这里并没有使用o[0]来表示第一个元素而是使用了一个while循环来找到有下标的第一个元素。

    // 这样的数组没有0-4的下标索引,所以第一个previousValue为6 let arr=new Array(5) arr[5]=6

  3. 之后使用while (k < len)来遍历数组的每一项元素,循环里再次使用(k in o)来排除empty元素。

这里学到了一个细节之处,new Array(5)构造的数组是没有0-4的索引下标的,所以reduce使用k < len && !(k in o)来排除数组中的empty元素。

reduce实战使用

  • 将多个promise形成的数组按先后顺序执行

    const f1 = () => new Promise(resolve => {

        setTimeout(() => {
            console.log(new Date(), "new Date()");
            console.log("加载第一份数据");
            resolve();
        }, 2000);
    });
    const f2 = () => (
        setTimeout(() => {
            new Promise(resolve => {
                console.log(new Date(), "new Date()");
                console.log("加载第二份数据");
                resolve();
            });
        }, 1000)
    );
    const runPromiseInSequence = array => array.reduce((pre, cur) => pre.then(cur), Promise.resolve());
    runPromiseInSequence([f1, f2]);
    

在实际开发过程中某些数据请求是有先后顺序,这个按顺序执行的runPromiseInSequence函数接收一个promise数组。核心是按顺序把函数放在Promise().then()里面来保证执行顺序。

  • 功能型函数管道

    const double = x => x + 2; const triple = x => x + 3;

    const pipe = (...functions) => input => functions.reduce(
        (acc, fn) => fn(acc),
        input
    );
    
    let doubleAndTriple = pipe(double,triple);
    let value = doubleAndTriple(1);
    console.log(value) //6
    

使用reduce封装多个函数的集合体,类似一个一个管道进行加工,依次执行每个函数。