JS Array.prototype.reduce的一些理解

2,965 阅读4分钟

Array.prototype.reduce在以前用的不多,在字节跳动面试的时候问到了这个问题,后面就去看了下 MDN,稍微对它理解了些

用法

reduce方法将数组从左到右的每个元素依次传入回调函数

👇是些常用到的地方

统计字符串中每个字符出现的次数

方法1 (这种方法是我没接触Array.prototype.reduce最常用的方法):

   const str = '9kFZTQLbUWOjurz9IKRdeg28rYxULHWDUrIHxCY6tnHleoJ'
   const obj = {}
   str.split('').forEach(item => {
       obj[item] ? obj[item]++ : obj[item] = 1
   })

方法2 (这个好玩一点点🤪):

  const str = '9kFZTQLbUWOjurz9IKRdeg28rYxULHWDUrIHxCY6tnHleoJ'
  const obj = {}
  Array.from(str).reduce((accumulator, current) => {
    current in accumulator ? accumulator[current]++ : accumulator[current] = 1
    return accumulator  
  }, obj)

嗯,方法2虽然步骤比方法1复杂些(其实并不复杂吼),但是是不是更好玩些😉?

筛选数组中同时满足多个条件的数据

方法1 (常用):

   const arr = [
      {
        "name": "a1111",
        "age": 25
      },
      {
        "name": "a1",
        "age": 26
      },
      {
        "name": "a11",
        "age": 27
      },
      {
        "name": "a",
        "age": 29
      },
      {
        "name": "a11",
        "age": 29
      },
      {
        "name": "a11",
        "age": 26
      },
      {
        "name": "a111",
        "age": 25
      },
      {
        "name": "a11",
        "age": 26
      },
      {
        "name": "a1",
        "age": 26
      },
      {
        "name": "a",
        "age": 26
      }
    ]
    
    arr.filter(item => item.name.length === 3)
       .filter(item => item.age > 26)
    /*
    [
      {
        "name": "a11",
        "age": 27
      },
      {
        "name": "a11",
        "age": 29
      }
    ]
    */

方法2 reduce方法(感谢@zhanggenming的指证,已修改):

    const arr = [
      {
        "name": "a1111",
        "age": 25
      },
      {
        "name": "a1",
        "age": 26
      },
      {
        "name": "a11",
        "age": 27
      },
      {
        "name": "a",
        "age": 29
      },
      {
        "name": "a11",
        "age": 29
      },
      {
        "name": "a11",
        "age": 26
      },
      {
        "name": "a111",
        "age": 25
      },
      {
        "name": "a11",
        "age": 26
      },
      {
        "name": "a1",
        "age": 26
      },
      {
        "name": "a",
        "age": 26
      }
    ]
    const filter1 = (arr) => arr.filter(item => item.name.length === 3)
    const filter2 = (arr) => arr.filter(item => item.age > 26)
    const fnArr = [filter1, filter2]
    fnArr.reduce((accumulator, fn) => {
     accumulator = fn(accumulator)
     return accumulator
    }, arr)
   /*
    [
      {
        "name": "a11",
        "age": 27
      },
      {
        "name": "a11",
        "age": 29
      }
    ]
    */

用了这个方法,那我们试试看用for循环来实现Array.prortotype.reduce

先看看reduce接收的参数:

  arr.reduce(callback[, initialValue])

reduce方法接收1个callback的函数作为第一个参数,还有1个可选参数initialValue。 同时callback函数有最多4个参数

  • accumulator 累加器累加回调的返回值;它是在最后一次调用回调时返回的累计值。如果提供了initialValue,它的默认值就是initialValue,否则就是数组的第一个值
  • currentValue 当前参与计算的元素
  • currentIndex 当前参与计算的元素的数组下标
  • array 当前参与运算的数组

知道了这些,那我们实现reduce方法就很简单了

    Array.prototype.selfReduce = function() {
       const ary = this
       const { length } = ary
       if (arguments.length === 0) {
         throw new TypeError('undefined is not a function')
       }
       if (typeof arguments[0] !== 'function') {
         throw new TypeError(arguments[0] + 'is not a function')
       }
       if (ary.length === 0 && arguments.length === 1) {
         throw new TypeError('Reduce of empty array with no initial value')
       }
       const callback = arguments[0]
       const startIndex = arguments.length >= 2 ? 0 : 1
       let value = startIndex === 0 ? arguments[1] : ary[0]
       for (let i = startIndex; i < length; i++) {
          value = callback(value, ary[i], i, ary)
       }
       return value
    }

同时,reduce还有个兄弟👬reduceRight,reduceRight如其名,是将数组从右到左的将每个元素传入callback函数。那实现reduceRight实现起来也就简单了。

Array.prototype.selfReduceRight = function () {
   const ary = this
   const { length } = ary
   if (arguments.length === 0) {
     throw new TypeError('undefined is not a function')
   }
   if (typeof arguments[0] !== 'function') {
     throw new TypeError(arguments[0] + 'is not a function')
   }
   if (ary.length === 0 && arguments.length === 1) {
       throw new TypeError('Reduce of empty array with no initial value')
   }
   const startIndex = arguments.length >= 2 ? length - 1 : length - 2
   const callback = arguments[0]
   let value = startIndex === 0 ? arguments[1] : ary[length - 1]
   for (let i = startIndex; i >= 0; i--) {
      value = callback(value, ary[i], i, ary)
   }
   return value
}

写在最后

穿插一点函数式编程(FP)里面的东西

  • compose(组合)
  • pipe(管道)

compose

compose的执行方向是从右向左执行的

下面是compose的实现(未处理异常情况)

const compose = function(...fns) {
    return (val) => {
        return fns.reduceRight((acc, fn) => {
            return fn(acc);
        }, val)
    }
};

const add1 = x => x + 1;
const mult2 = y => y * 2;

const composeFn = compose(add1, mult2);

composeFn(5); // 11 = 5 * 2 + 1

pipe

如同Unix里面的"|"一样,pipe是从左向右执行的。下面是一个很简单的查看nginx进程的命令(写的有点挫😂)。

    ps -ef | grep nginx

下面是pipe的实现(未处理异常情况)

const pipe = function(...fns) {
    return (val) => {
        return fns.reduce((acc, fn) => {
            return fn(acc);
        }, val)
    }
};

const add1 = x => x + 1;
const mult2 = y => y * 2;

const pipeFn = pipe(add1, mult2);

pipeFn(5); // 12 = (5 + 1) * 2

嗯,这篇文章就写完了,也不知道写了些什么,在掘金写文章不多,所以不知道各位看官的口味,如果文章有写的不对或者写的不好的地方,烦请各位看官指出,我也好改正🙏。