Javascript數(shù)組方法reduce的妙用之處分享
前言
Javascript數(shù)組方法中,相比map、filter、forEach等常用的迭代方法,reduce常常被我們所忽略,今天一起來探究一下reduce在我們實(shí)戰(zhàn)開發(fā)當(dāng)中,能有哪些妙用之處,下面從reduce語(yǔ)法開始介紹。
語(yǔ)法
array.reduce(function(accumulator, arrayElement, currentIndex, arr), initialValue)
若傳入初始值,accumulator首次迭代就是初始值,否則就是數(shù)組的第一個(gè)元素;后續(xù)迭代中將是上一次迭代函數(shù)返回的結(jié)果。所以,假如數(shù)組的長(zhǎng)度為n,如果傳入初始值,迭代次數(shù)為n;否則為n-1。
比如實(shí)現(xiàn)數(shù)組 arr = [1,2,3,4] 求數(shù)組的和
let arr = [1,2,3,4]; arr.reduce(function(pre,cur){return pre + cur}); // return 10
實(shí)際上reduce還有很多重要的用法,這是因?yàn)槔奂悠鞯闹悼梢圆槐貫楹?jiǎn)單類型(如數(shù)字或字符串),它也可以是結(jié)構(gòu)化類型(如數(shù)組或?qū)ο螅?,這使得我們可以用它做一些其他有用的事情,比如:
- 將數(shù)組轉(zhuǎn)換為對(duì)象
- 展開更大的數(shù)組
- 在一次遍歷中進(jìn)行兩次計(jì)算
- 將映射和過濾函數(shù)組合
- 按順序運(yùn)行異步函數(shù)
將數(shù)組轉(zhuǎn)化為對(duì)象
在實(shí)際業(yè)務(wù)開發(fā)中,你可能遇到過這樣的情況,后臺(tái)接口返回的數(shù)組類型,你需要將它轉(zhuǎn)化為一個(gè)根據(jù)id值作為key,將數(shù)組每項(xiàng)作為value的對(duì)象進(jìn)行查找。
例如:
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這個(gè)庫(kù),使用_.keyBy這個(gè)方法就能進(jìn)行轉(zhuǎn)換,但用reduce也能實(shí)現(xiàn)這樣的需求。
function keyByUsernameReducer(acc, person) { return {...acc, [person.id]: person}; } const userObj = peopleArr.reduce(keyByUsernameReducer, {}); console.log(userObj);
將小數(shù)組展開成大數(shù)組
試想這樣一個(gè)場(chǎng)景,我們將一堆純文本行讀入數(shù)組中,我們想用逗號(hào)分隔每一行,生成一個(gè)更大的數(shù)組名單。
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" // ]
我們從長(zhǎng)度為5的數(shù)組開始,最后得到一個(gè)長(zhǎng)度為16的數(shù)組。
另一種常見增加數(shù)組的情況是flatMap,有時(shí)候我們用map方法需要將二級(jí)數(shù)組展開,這時(shí)可以用reduce實(shí)現(xiàn)扁平化
例如:
Array.prototype.flatMap = function(f) { const reducer = (acc, item) => acc.concat(f(item)); return this.reduce(reducer, []); } const arr = ["今天天氣不錯(cuò)", "", "早上好"] const arr1 = arr.map(s => s.split("")) // [["今", "天", "天", "氣", "不", "錯(cuò)"],[""],["早", "上", "好"]] const arr2 = arr.flatMap(s => s.split('')); // ["今", "天", "天", "氣", "不", "錯(cuò)", "", "早", "上", "好"]
在一次遍歷中進(jìn)行兩次計(jì)算
有時(shí)我們需要對(duì)數(shù)組進(jìn)行兩次計(jì)算。例如,我們可能想要計(jì)算數(shù)字列表的最大值和最小值。我們可以通過兩次通過這樣做:
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}
這需要遍歷我們的數(shù)組兩次。但是,有時(shí)我們可能不想這樣做。因?yàn)?reduce()讓我們返回我們想要的任何類型,我們不必返回?cái)?shù)字。我們可以將兩個(gè)值編碼到一個(gè)對(duì)象中。然后我們可以在每次迭代時(shí)進(jìn)行兩次計(jì)算,并且只遍歷數(shù)組一次:
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}
將映射和過濾合并為一個(gè)過程
還是先前那個(gè)用戶列表,我們希望找到?jīng)]有電子郵件地址的人的用戶名,返回它們用戶名用逗號(hào)拼接的字符串。一種方法是使用兩個(gè)單獨(dú)的操作:
- 獲取過濾無電子郵件后的條目
- 獲取用戶名并拼接
將它們放在一起可能看起來像這樣:
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'
現(xiàn)在,這段代碼是完全可讀的,對(duì)于小的樣本數(shù)據(jù)不會(huì)有性能問題,但是如果我們有一個(gè)龐大的數(shù)組呢?如果我們修改我們的reducer回調(diào),那么我們可以一次完成所有事情:
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'
在這個(gè)版本中,我們只遍歷一次數(shù)組,一般建議使用filter和map的組合,除非發(fā)現(xiàn)性能問題,才推薦使用reduce去做優(yōu)化。
按順序運(yùn)行異步函數(shù)
我們可以做的另一件事.reduce()是按順序運(yùn)行promises(而不是并行)。如果您對(duì)API請(qǐng)求有速率限制,或者您需要將每個(gè)prmise的結(jié)果傳遞到下一個(gè)promise,reduce可以幫助到你。
舉一個(gè)例子,假設(shè)我們想要為userList數(shù)組中的每個(gè)人獲取消息。
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函數(shù)返回一個(gè) Promise 對(duì)象,可以使用then方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到await就會(huì)先返回,等到異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語(yǔ)句。
請(qǐng)注意,在此我們傳遞Promise作為初始值Promise.resolve(),我們的第一個(gè)API調(diào)用將立即運(yùn)行。
下面是不使用async語(yǔ)法糖的版本
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: [ … ]}
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
如何用js獲取當(dāng)前域名、Url、相對(duì)路徑和參數(shù)
這篇文章主要給大家介紹了關(guān)于如何用js獲取當(dāng)前域名、Url、相對(duì)路徑和參數(shù)的相關(guān)資料,用Javascript可以單獨(dú)獲取當(dāng)前域名、Url相對(duì)路徑和參數(shù),文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11對(duì)象題目的一個(gè)坑 理解Javascript對(duì)象
這篇文章主要介紹了Javascript對(duì)象,特別為大家分享了對(duì)象題目的一個(gè)坑,提供了解題思路,感興趣的小伙伴們可以參考一下2015-12-12Javascript設(shè)計(jì)模式理論與編程實(shí)戰(zhàn)之簡(jiǎn)單工廠模式
簡(jiǎn)單工廠模式是由一個(gè)方法來決定到底要?jiǎng)?chuàng)建哪個(gè)類的實(shí)例, 而這些實(shí)例經(jīng)常都擁有相同的接口. 這種模式主要用在所實(shí)例化的類型在編譯期并不能確定, 而是在執(zhí)行期決定的情況。 說的通俗點(diǎn),就像公司茶水間的飲料機(jī),要咖啡還是牛奶取決于你按哪個(gè)按鈕2015-11-11javascript中幾個(gè)容易混淆的概念總結(jié)
這篇文章主要介紹了javascript中幾個(gè)容易混淆的概念總結(jié),都是平時(shí)經(jīng)常遇到的問題,這里推薦給大家,有需要的小伙伴參考下吧。2015-04-04JavaScript實(shí)現(xiàn)簡(jiǎn)單驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)簡(jiǎn)單驗(yàn)證碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08Express實(shí)現(xiàn)前端后端通信上傳圖片之存儲(chǔ)數(shù)據(jù)庫(kù)(mysql)傻瓜式教程(二)
這篇文章主要介紹了Express實(shí)現(xiàn)前端后端通信上傳圖片之存儲(chǔ)數(shù)據(jù)庫(kù)(mysql)傻瓜教程(二)的相關(guān)資料,需要的朋友可以參考下2015-12-12js trim函數(shù) 去空格函數(shù)與正則集錦
在javascript中處理文本框輸入值的時(shí)候,經(jīng)常要用到"去掉前后空白"的功能。用過jQuery的朋友都知道,jQuery提供了一個(gè)trim()這樣的功能函數(shù),可以很輕松幫我們實(shí)現(xiàn)這樣的效果。2009-11-11