【掰扯reduce】可能不知道的reduce

447 阅读3分钟

学习总结篇,以能否造轮子衡量学习效果。本篇是在看到koa-composeredux里均有reduce的影子,才计划输出的

reduce 实现

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

reducer 函数接收4个参数:

  1. Accumulator (acc) (累计器)
  2. Current Value (cur) (当前值)
  3. Current Index (idx) (当前索引)
  4. Source Array (src) (源数组)

reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。

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

实现一个reduce

  Array.prototype.reduce1 = function (callback, initValue) {
    // 调用的array
    let curArr = this
    // 上一次的返回值
    let returnValue
    // 起点
    let startIndex

    // 空数组-并且有初始值
    if(curArr.length === 0) {
      return initValue
    }

    // 判断是否有传第二个参数(初始值)
    returnValue = typeof initValue === 'undefined' ? curArr[0] : initValue
    
    // 初始迭代位置
    startIndex = typeof initValue === 'undefined' ? 1 : 0
    
    for (let index = startIndex; index < curArr.length; index++) {
      // 依次执行callback 
      returnValue = callback(returnValue, curArr[index], index, curArr)
    }

    return returnValue
  }
  
  console.log('reduce', [1,2,3].reduce1((pre, cur) => pre + cur, 1))
  // 7

reduce实现promise顺序执行

想到顺序执行promise, 当然是可以用async await实现。本文主要谈的是reduce的实现。

runQueuePromise方法将会被一个每一项都返回一个 Promise 的数组调用,并且依次执行数组中的每一个 Promise

  const f1 = () => new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('f1 running')
      resolve(1)
    })
  })

  const f2 = () => new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('f2 running')
      resolve(1)
    })
  })

  const fnArray = [f1, f2]

  function runQueuePromise(arr, initValue) {
    return arr.reduce((preChain, curFunction) => preChain.then(curFunction), Promise.resolve(initValue))
  }

  runQueuePromise(fnArray, 'initvalue')

reduce实现pipe

pipe,是把一些小功能拼筹在一起,组成我们自己需要的功能。

例:

const fn1 = x => x + 1
const fn2 = x => x + 2

我们想让一个参数通过第一个函数之后再通过第二个函数,最直接最简单的方法是:

f2(f1(1)) // 4

写一个简单的pipe函数,它返回一个新的函数,来达到我们上面的目的

pipe 的实现:pipe(f1, f2, f3) 是一个 curry 化函数,它返回一个新的函数,这个新的函数将会完成 (...args) => f3(f2(f1(...args))) 的调用。

const fn1 = x => x + 1
const fn2 = x => x + 2

const pipe = (...funcs) => input => funcs.reduce(
    (f1, f2) => f2(f1),
    input
)

const newPipe = pipe(fn1, fn2)
console.log('pipe:', newPipe(1))

reduce实现compose

compose 和前面提到的 pipe 一样,就是执行一连串任务(方法),比如:

let funcs = [fn1, fn2, fn3, fn4]
let composeFunc = compose(...funcs)
composeFunc(args)
fn1(fn2(fn3(fn4(args))))

// compose
fn1(fn2(fn3(fn4(args))))

// pipe
fn4(fn3(fn2(fn1(args))))

唯一的不同就是执行顺序

1. 面向过程的实现:递归

用到了闭包,使用闭包变量储存结果 result 和函数数组长度以及遍历索引,并利用递归思想,进行结果的累加计算。

function compose(...args) {
  // 函数数组长度。闭包变量函数数组长度以及遍历索引
  let len = args.length
  // 需要执行的函数个数:总数 - 1(第一次进来,已执行一个)
  let total = len - 1
  // 闭包变量储存结果 result
  let result

  return function fn(...args1) {
    // 从右往左执行函数
    result = args[total].apply(this, args1)
    // 函数数组执行完毕
    if (total <= 0) {
      total = len - 1
      return total
    }
    total--
    return fn.call(null, result)
  }
}

2. redux版本的实现

充分利用了reduce方法,个人最喜欢的写法

// Redux 版本
function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg
    }

    if (funcs.length === 1) {
        return funcs[0]
    }

    return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

3. koa-compose版本的实现

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

至此,reduce的用处还真不少。

PS:欢迎交流学习,不足之处,尽请指出。