Javascript數組方法中,相比map
、filter
、forEach
等經常使用的迭代方法,reduce
經常被咱們所忽略,今天一塊兒來探究一下reduce
在咱們實戰開發當中,能有哪些妙用之處,下面從reduce
語法開始介紹。前端
array.reduce(function(accumulator, arrayElement, currentIndex, arr), initialValue)
若傳入初始值,accumulator首次迭代就是初始值,不然就是數組的第一個元素;後續迭代中將是上一次迭代函數返回的結果。因此,假如數組的長度爲n,若是傳入初始值,迭代次數爲n;不然爲n-1。json
好比實現數組 arr = [1,2,3,4] 求數組的和api
let arr = [1,2,3,4]; arr.reduce(function(pre,cur){return pre + cur}); // return 10
實際上reduce還有不少重要的用法,這是由於累加器的值能夠沒必要爲簡單類型(如數字或字符串),它也能夠是結構化類型(如數組或對象),這使得咱們能夠用它作一些其餘有用的事情,好比:數組
在實際業務開發中,你可能遇到過這樣的狀況,後臺接口返回的數組類型,你須要將它轉化爲一個根據id值做爲key,將數組每項做爲value的對象進行查找。promise
例如:異步
const userList = [ { id: 1, username: 'john', sex: 1, email: 'john@163.com' }, { id: 2, username: 'jerry', sex: 1, email: 'jerry@163.com' }, { id: 3, username: 'nancy', sex: 0, email: '' } ];
若是你用過lodash這個庫,使用_.keyBy
這個方法就能進行轉換,但用reduce
也能實現這樣的需求。async
function keyByUsernameReducer(acc, person) { return {...acc, [person.id]: person}; } const userObj = peopleArr.reduce(keyByUsernameReducer, {}); console.log(userObj);
試想這樣一個場景,咱們將一堆純文本行讀入數組中,咱們想用逗號分隔每一行,生成一個更大的數組名單。函數
const fileLines = [ 'Inspector Algar,Inspector Bardle,Mr. Barker,Inspector Barton', 'Inspector Baynes,Inspector Bradstreet,Inspector Sam Brown', 'Monsieur Dubugue,Birdy Edwards,Inspector Forbes,Inspector Forrester', 'Inspector Gregory,Inspector Tobias Gregson,Inspector Hill', 'Inspector Stanley Hopkins,Inspector Athelney Jones' ]; function splitLineReducer(acc, line) { return acc.concat(line.split(/,/g)); } const investigators = fileLines.reduce(splitLineReducer, []); console.log(investigators); // [ // "Inspector Algar", // "Inspector Bardle", // "Mr. Barker", // "Inspector Barton", // "Inspector Baynes", // "Inspector Bradstreet", // "Inspector Sam Brown", // "Monsieur Dubugue", // "Birdy Edwards", // "Inspector Forbes", // "Inspector Forrester", // "Inspector Gregory", // "Inspector Tobias Gregson", // "Inspector Hill", // "Inspector Stanley Hopkins", // "Inspector Athelney Jones" // ]
咱們從長度爲5的數組開始,最後獲得一個長度爲16的數組。性能
另外一種常見增長數組的狀況是flatMap,有時候咱們用map方法須要將二級數組展開,這時能夠用reduce實現扁平化fetch
例如:
Array.prototype.flatMap = function(f) { const reducer = (acc, item) => acc.concat(f(item)); return this.reduce(reducer, []); } const arr = ["今每天氣不錯", "", "早上好"] const arr1 = arr.map(s => s.split("")) // [["今", "天", "天", "氣", "不", "錯"],[""],["早", "上", "好"]] const arr2 = arr.flatMap(s => s.split('')); // ["今", "天", "天", "氣", "不", "錯", "", "早", "上", "好"]
有時咱們須要對數組進行兩次計算。例如,咱們可能想要計算數字列表的最大值和最小值。咱們能夠經過兩次經過這樣作:
const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4]; const maxReading = readings.reduce((x, y) => Math.max(x, y), Number.MIN_VALUE); const minReading = readings.reduce((x, y) => Math.min(x, y), Number.MAX_VALUE); console.log({minReading, maxReading}); // {minReading: 0.2, maxReading: 5.5}
這須要遍歷咱們的數組兩次。可是,有時咱們可能不想這樣作。由於.reduce()讓咱們返回咱們想要的任何類型,咱們沒必要返回數字。咱們能夠將兩個值編碼到一個對象中。而後咱們能夠在每次迭代時進行兩次計算,而且只遍歷數組一次:
const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4]; function minMaxReducer(acc, reading) { return { minReading: Math.min(acc.minReading, reading), maxReading: Math.max(acc.maxReading, reading), }; } const initMinMax = { minReading: Number.MAX_VALUE, maxReading: Number.MIN_VALUE, }; const minMax = readings.reduce(minMaxReducer, initMinMax); console.log(minMax); // {minReading: 0.2, maxReading: 5.5}
仍是先前那個用戶列表,咱們但願找到沒有電子郵件地址的人的用戶名,返回它們用戶名用逗號拼接的字符串。一種方法是使用兩個單獨的操做:
將它們放在一塊兒可能看起來像這樣:
function notEmptyEmail(x) { return !!x.email } function notEmptyEmailUsername(a, b) { return a ? `${a},${b.username}` : b.username } const userWithEmail = userList.filter(notEmptyEmail); const userWithEmailFormatStr = userWithEmail.reduce(notEmptyEmailUsername, ''); console.log(userWithEmailFormatStr); // 'john,jerry'
如今,這段代碼是徹底可讀的,對於小的樣本數據不會有性能問題,可是若是咱們有一個龐大的數組呢?若是咱們修改咱們的reducer回調,那麼咱們能夠一次完成全部事情:
function notEmptyEmail(x) { return !!x.email } function notEmptyEmailUsername(usernameAcc, person){ return (notEmptyEmail(person)) ? (usernameAcc ? `${usernameAcc},${person.username}` : `${person.username}`) : usernameAcc; } const userWithEmailFormatStr = userList.reduce(notEmptyEmailUsername, ''); console.log(userWithEmailFormatStr); // 'john,jerry'
在這個版本中,咱們只遍歷一次數組,通常建議使用filter
和map
的組合,除非發現性能問題,才推薦使用reduce
去作優化。
咱們能夠作的另外一件事.reduce()是按順序運行promises(而不是並行)。若是您對API請求有速率限制,或者您須要將每一個prmise的結果傳遞到下一個promise,reduce
能夠幫助到你。
舉一個例子,假設咱們想要爲userList
數組中的每一個人獲取消息。
function fetchMessages(username) { return fetch(`https://example.com/api/messages/${username}`) .then(response => response.json()); } function getUsername(person) { return person.username; } async function chainedFetchMessages(p, username) { // In this function, p is a promise. We wait for it to finish, // then run fetchMessages(). const obj = await p; const data = await fetchMessages(username); return { ...obj, [username]: data}; } const msgObj = userList .map(getUsername) .reduce(chainedFetchMessages, Promise.resolve({})) .then(console.log); // {glestrade: [ … ], mholmes: [ … ], iadler: [ … ]}
async
函數返回一個 Promise 對象,可使用then
方法添加回調函數。當函數執行的時候,一旦遇到await
就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。
請注意,在此咱們傳遞Promise做爲初始值Promise.resolve()
,咱們的第一個API調用將當即運行。
下面是不使用async
語法糖的版本
function fetchMessages(username) { return fetch(`https://example.com/api/messages/${username}`) .then(response => response.json()); } function getUsername(person) { return person.username; } function chainedFetchMessages(p, username) { // In this function, p is a promise. We wait for it to finish, // then run fetchMessages(). return p.then((obj)=>{ return fetchMessages(username).then(data=>{ return { ...obj, [username]: data } }) }) } const msgObj = peopleArr .map(getUsername) .reduce(chainedFetchMessages, Promise.resolve({})) .then(console.log); // {glestrade: [ … ], mholmes: [ … ], iadler: [ … ]}
PS:更多前端資訊、技術乾貨,請關注公衆號「前端新視界」