JS數組Reduce方法詳解

概述

一直以來都在函數式編程的大門以外徘徊,要入門的話首先得熟悉各類高階函數,數組的reduce方法就是其中之一。前端

reduce方法將會對數組元素從左到右依次執行reducer函數,而後返回一個累計的值。舉個形象的例子:你要組裝一臺電腦,買了主板、CPU、顯卡、內存、硬盤、電源...這些零件是組裝電腦的必要條件。編程

裝的過程能夠簡單歸納爲拆掉每一個零件的包裝,再裝到一塊兒。類比一下reduce函數就能夠明白了,那些零件就至關於要執行reduce方法的數組,對每一個零件執行拆除包裝的加工程序,就是對數組的每一個元素執行reducer函數,把這些零件裝到一塊兒,就至關於reduce方法的任務,最終組裝好的電腦至關於reduce方法的返回值。redux

reduce方法接收兩個參數,第一個參數是回調函數reducer,第二個參數是初始值。reducer函數接收四個參數:數組

  • Accumulator:MDN上解釋爲累計器,但我以爲不恰當,按個人理解它應該是截至當前元素,以前全部的數組元素被reducer函數處理累計的結果
  • Current:當前被執行的數組元素
  • CurrentIndex: 當前被執行的數組元素索引
  • SourceArray:原數組,也就是調用reduce方法的數組

若是傳入第二個參數,reduce方法會在這個參數的基礎上開始累計執行。promise

概念講了那麼多,那reduce它的執行機制是怎樣的呢?彆着急,從用法入手一點一點分析。app

來個最好理解的例子:數組求和koa

const arr = [1, 2, 3, 4]
    const accumulator = (total, current, currentIndex, arr) => {
      console.log(total, current, currentIndex, arr);
      return total + current
    }
    console.log(arr.reduce(accumulator))

執行結果以下:函數式編程

img

很明確,最終的結果就是把全部數組的元素都加起來。值得注意的是,它將數組的第一個元素做爲累加的初始值,而後再依次對後邊的元素執行reducer函數。函數

總共執行了三次,得出最終結果。那若是傳入初始值,是怎樣的執行順序?this

console.log(arr.reduce(accumulator, 3))

結果以下:

img

此次是以傳入的初始值做爲累加的起點,而後依次對數組的元素執行reducer。

假設對沒有初始值的空數組調用reduce方法,則會報錯:

Uncaught TypeError: Reduce of empty array with no initial value

一些用法

講了一些概念,但使用場景更重要,下面來看一下reduce方法都會有哪些用途。

compose函數

compose是函數式編程的一種形式,用於將多個函數合併,上一個函數的返回值做爲當前函數的入參,當前函數的返回值再做爲下一個函數的入參,這樣的效果有點相似於koa中間件的洋蔥模型。

[a, b, c, d] => a(b(c(d())))

實際上和累加差很少,只不過把累加操做變成了入參執行,相加的結果變成了執行的返回值。redux的applyMiddleware內就使用了compose,目的是保證最終的dispatch是被全部中間件處理後的結果。

下面來以applyMiddleware中的compose爲例,先看用法:

const result = compose(a, b, c)(params)

執行狀況是這樣:

(params) => a(b(c(params)))

返回的是一個函數,將params做爲該函數的入參,被右側第一個函數執行,執行順序是從右到左執行,其他的函數的參數都是上一個函數的返回值。

看一下實現:

function compose(...funcs) {
  // funcs是compose要組合的那些函數,arg是componse返回的函數的參數
  if (funcs.length === 0) {
    // 若是沒有傳入函數,那麼直接返回一個函數,函數的返回值就是傳進來的參數
    return arg => arg
  }
  if (funcs.length === 1) {
    // 若是隻傳入了一個函數,直接返回這個函數
    return funcs[0]
  }

  return funcs.reduce((all, current) => {
    return (...args) => {
      return all(current(...args))
    }
  })
}

扁平化數組

const array = [[0, 1], [2, 3], [4, 5]]
const flatten = arr => {
  return arr.reduce((a, b) => {
    return a.concat(b)
  }, [])
}
console.log(flatten(array)); // [0, 1, 2, 3, 4, 5]

來看一下執行過程,

  • 第一次執行,初始值傳入[],走到reduce的回調裏,參數a就這個[],參數b是數組第一項[0, 1],回調內[].cancat([0, 1])
  • 第二次執行,reduce的回調參數a是上一次回調執行的結果[0, 1],本次繼續用它來concat數組的第二項[2, 3],獲得結果[0, 1, 2, 3]做爲下一次回調執行的參數a繼續執行下去
  • ...以此類推

那麼假設數組是這樣呢?[[0, [111, 222], 1], [2, [333, [444, 555]], 3], [4, 5]],其實加個遞歸調用就能夠

const array = [[0, [111, 222], 1], [2, [333, [444, 555]], 3], [4, 5]]

const flatten = arr => {
  return arr.reduce((a, b) => {
    if (b instanceof Array) {
      return a.concat(flatten(b))
    }
    return a.concat(b)
  }, [])
}
console.log(flatten(array)); // [0, 111, 222, 1, 2, 333, 444, 555, 3, 4, 5]

統計字符串中每一個字符出現的次數

每次回調執行的時候,都會往對象中加一個key爲字符串,value爲出現次數的鍵值,若已經存儲過字符串,那麼它的value加1。

const str = 'adefrfdnnfhdueassjfkdiskcddfjds'
const arr = str.split('')
const strObj = arr.reduce((all, current) => {
  if (current in all) {
    all[current]++
  } else {
    all[current] = 1
  }
  return all
}, {})

console.log(strObj) // {"a":2,"d":7,"e":2,"f":5,"r":1,"n":2,"h":1,"u":1,"s":4,"j":2,"k":2,"i":1,"c":1}

數組去重

const arr = ['1', 'a', 'c', 'd', 'a', 'c', '1']
const afterUnique = arr.reduce((all, current) => {
  if (!all.includes(current)) {
    all.push(current)
  }
  return all
}, [])
console.log(afterUnique); //  ["1", "a", "c", "d"]

按照順序調用promise

這種方式實際上處理的是promise的value,將上一個promise的value做爲下一個promise的value進行處理。

const prom1 = a => {
  return new Promise((resolve => {
    resolve(a)
  }))
}
const prom2 = a => {
  return new Promise((resolve => {
    resolve(a * 2)
  }))
}
const prom3 = a => {
  return new Promise((resolve => {
    resolve(a * 3)
  }))
}

const arr = [prom1, prom2, prom3]
const result = arr.reduce((all, current) => {
  return all.then(current)
}, Promise.resolve(10))

result.then(res => {
  console.log(res);
})

實現

經過上面的用法,能夠總結出來reduce的特色:

  • 接收兩個參數,第一個爲函數,函數內會接收四個參數:Accumulator Current CurrentIndex SourceArray,第二個參數爲初始值。
  • 返回值爲一個全部Accumulator累計執行的結果
Array.prototype.myReduce = function(fn, base) {
    if (this.length === 0 && !base) {
      throw new Error('Reduce of empty array with no initial value')
    }
    for (let i = 0; i < this.length; i++) {
      if (!base) {
        base = fn(this[i], this[i + 1], i, this)
        i++
      } else {
        base = fn(base, this[i], i, this)
      }
    }
    return base
  }

更多技術文章能夠關注公衆號:一口一個前端

qrcode-small.jpg

相關文章
相關標籤/搜索