你应该知道的JS: reduce的n种应用

3,659 阅读3分钟

reduce是ES5中新引入的一个API。

假如你还不知道reduce的用法,请先阅读下MDN文档中关于reduce的介绍。(不得不说,MDN文档太强大了,里面列举了很多有用的方法)

本文将介绍借助reduce函数,利用其能够遍历到数组的每一个元素,并且次遍历都可以使用上次遍历结果的特性,实现的一些功能。

1.累和/累积

let arr = [1, 2, 3, 4, 5]

console.log(arr.reduce((prev, cur) => prev + cur)) // 15

// 可以实现另类的阶乘
console.log(arr.reduce((prev, cur) => prev * cur)) // 120

2.求最大值/最小值

let arr = [1, 2, 3, 4, 5]

console.log(arr.reduce((prev, cur) => Math.max(prev, cur))); // 5

console.log(arr.reduce((prev, cur) => Math.min(prev, cur))); // 1

3. 数组去重

reduce接收两个参数时,即reduce(fn, init), init将作为fn的第一个参数prev传入。

这里,将一个空数组[]作为去重后的新数组,通过做判断,如果该容器内已经存在某元素,就啥也不做;反之,如果该容器内还没有一个元素,就将其推入容器。

let arr = [1, 2, 3, 1, 1, 2, 3, 3, 4, 3, 4, 5]
let res = arr.reduce((prev, cur)=>{
  !prev.includes(cur) && prev.push(cur)
  return prev
}, [])

console.log(res) // [ 1, 2, 3, 4, 5 ]

4.实现map函数

map函数接收一个函数作为参数,作为参数的函数接收三个参数值,分别是遍历数组的每一项元素,元素的索引和数组本身。这三个参数刚好和reduce函数接收的第一个函数参数的第2、3、4个参数是对应的。

实现思路是,将每次遍历的元素,作为传入的函数的参数,并将函数执行的结果放入新的数组中

let arr = [1, 2, 3]
Array.prototype._map = function(cb) {
  if(typeof cb === 'function') {
    // this: 调用_map方法的当前数组对象
    let arr = this;
    return arr.reduce((prev, item, index, array) => {
      prev.push(cb(item, index, array))
      return prev
    }, [])
  } else {
    throw new Error(cb + ' is not function')
  }
}

let res = arr._map(n => n*2)
console.log(res) // [ 2, 4, 6 ]

5.实现filter函数

实现filter的思路和实现map是一致的,只不过后者是一股脑的把执行结果全放入数组中,而filter需要做一个判断:如果filter函数传入的参数(参数是一个函数)执行后有返回值,即经过了检验,才将遍历的当前元素放入数组中,如果没有返回值,就忽略

let arr = [1, 2, 3, 4, 5];

Array.prototype._filter = function(cb) {
  if(typeof cb === 'function') {
    let arr = this;
    return arr.reduce((prev, item, index, array) => {
        cb(item, index, array) ? prev.push(item) : null
        return prev
    }, [])
  } else {
    throw new Error(cb + ' is not function')
  }
}

let res = arr._filter(n => n>2)
console.log(res) // [ 3, 4, 5 ]

6.实现compose

compose是函数式编程的核心思想,简单说就是将若干个函数组合成一个函数来执行,并且每个函数执行的结果都能作为下一个函数的参数。这也是使用reduce实现compose的思路。

假设有两个函数,作用分别是将字符串转为大写,在字符串末尾追加感叹号:

function toUpperCase(str) {
  return str.toUpperCase();
}

function add(str) {
  return str += '!'
}

一般情况下,会这样使用:

var str = 'hello world'
var res = toUpperCase(str)
res = add(res)
console.log(res); // HELLO WORLD!

使用compose后,效果是这样的,执行fn,相当于依次执行了toUpperCaseadd:

var fn = compose(add, toUpperCase)
console.log(fn(str));// HELLO WORLD!

接下来实现一下compose:

function compose() {
  let args = [].slice.call(arguments)

  return function (x) {
   // 因为compose()接收的函数参数,是从右往走顺次执行的,
   // 所以这里使用reduceRight, 用法和reduce一致,只不过是从右往左遍历数组。
    return args.reduceRight((prev, cur) => {
      return cur(prev)
    }, x)
  }
}

7.数组扁平化

数组扁平化,针对的是多维数组,将其扁平、展开,成为一维数组。

let arr = [1, 2, '3js', [4, 5, [6], [7, 8, [9, 10, 11], null, 'abc'], {age: 12}, [13, 14]], '[]'];

function flatten(arr) {
  if(Array.isArray(arr)) {
    return arr.reduce((prev, cur) => {
       // 如果遍历的当前项是数组,再迭代展平
      return Array.isArray(cur) ? prev.concat(flatten(cur)) : prev.concat(cur)
    }, [])
  } else {
    throw new Error(arr + ' is not array')
  }
}

console.log(flatten(arr));

结束

当然,除了以上几种,reduce还有更多种神奇的应用,等待着各位小伙伴的发现和使用。