JavaScript 數(shù)組 reduce 總是不會用?看看這 5 個例子就懂了!
相信不少初學(xué)者曾經(jīng)都被 JavaScript 數(shù)組的 reduce方法困擾過,一直搞不明白到底怎么用。reduce方法是按順序?qū)?shù)組每個元素執(zhí)行某個函數(shù),這個函數(shù)接收上一次執(zhí)行結(jié)果作為參數(shù),并將結(jié)果傳給下一次調(diào)用。reduce方法用得好可以簡化復(fù)雜的邏輯,提高代碼可讀性。通過下面幾個例子可以幫你快速理解reduce的用法。
1.數(shù)字?jǐn)?shù)組求和
這是reduce最常見的入門級例子。如果用傳統(tǒng)的for循環(huán)是這樣的:
- function sum(arr) {
- let sum = 0;
- for (const val of arr) {
- sum += val;
- }
- return sum;
- }
- sum([1, 3, 5, 7]); // 16
如果用 reduce():
- function sum(arr) {
- const reducer = (sum, val) => sum + val;
- const initialValue = 0;
- return arr.reduce(reducer, initialValue);
- }
- sum([1, 3, 5, 7]); // 16
reduce()函數(shù)的第一個參數(shù)是一個reducer函數(shù),第二個參數(shù)是初始值。在每個數(shù)組元素上執(zhí)行reducer函數(shù),第一個參數(shù)是“累進(jìn)值”。累進(jìn)值的初始值是initialValue,并且在每一輪調(diào)用后更新為reducer函數(shù)的返回值。
為了幫助理解,可以用for循環(huán)實(shí)現(xiàn)一個簡單的reduce()函數(shù):
- function reduce(arr, reducer, initialValue) {
- let accumulator = initialValue;
- for (const val of array) {
- accumulator = reducer(accumulator, val);
- }
- return accumulator;
- }
2.對象數(shù)組數(shù)字屬性值求和
單看 reduce() 本身,大家更多的感受是它的晦澀難懂,而不是有多好用。如果僅僅是為了給數(shù)字?jǐn)?shù)組求和,用for循環(huán)可能來得更直觀。但是,當(dāng)你把它跟其他數(shù)組方法(比如filter和map)結(jié)合使用時,才能感受到它的強(qiáng)大和方便。
舉個栗子,假設(shè)有個對象數(shù)組,每個對象都有個total屬性。對這些total求和:
- const lineItems = [
- { description: 'Eggs (Dozen)', quantity: 1, price: 3, total: 3 },
- { description: 'Cheese', quantity: 0.5, price: 5, total: 2.5 },
- { description: 'Butter', quantity: 2, price: 6, total: 12 }
- ];
用reduce可以這樣寫:
- lineItems.reduce((sum, li) => sum + li.total, 0); // 17.5
這樣是能得到最終結(jié)果,但是代碼的可組合性就沒那么好,可以做些優(yōu)化,把total屬性提前提取出來:
- lineItems.map(li => li.total).reduce((sum, val) => sum + val, 0);
第二種方式為什么更好?因?yàn)檫@樣就可以把求和的邏輯抽象到一個單獨(dú)的函數(shù)sum()中,方便以后重用。
- //匯總 total
- lineItems.map(li => li.total).reduce(sumReducer, 0);
- // 匯總 quantity
- lineItems.map(li => li.quantity).reduce(sumReducer, 0);
- function sumReducer(sum, val) {
- return sum + val;
- }
這種抽象比較重要,因?yàn)槟阌X得 sumReducer() 邏輯不會變,其實(shí)不然。比如,這個求和邏輯并沒有考慮到0.1 + 0.2 !== 0.3的問題。(參考 為什么 0.1 + 0.2 = 0.300000004)如果有了這個抽象,要修復(fù)這個缺陷就比較方便了。比如:
- const { round } = require('lodash');
- function sumReducer(sum, val) {
- // 保留2位小數(shù)
- return _.round(sum + val, 2);
- }
3.求最大值
reduce()通常用來求和,但它的功能遠(yuǎn)不止這個。累進(jìn)值accumulator 可以是任意值:數(shù)字,null,undefined,數(shù)組,對象等。
舉個栗子,假設(shè)有個日期數(shù)組,要找出最近的一個日期:
- const dates = [
- '2019/06/01',
- '2018/06/01',
- '2020/09/01', // 這個是最近日期,但是怎么找到它?
- '2018/09/01'
- ].map(v => new Date(v));
一種方法是給數(shù)組排序,找最后一個值??瓷先タ尚校切蕸]那么高,并且用數(shù)組的默認(rèn)方法給日期對象排序其實(shí)是有點(diǎn)小問題的。不只是日期對象,任何類型的值在sort()方法中比較,都會默認(rèn)先轉(zhuǎn)成字符串比較,最終結(jié)果可能并不是你想要的。
- const a = [4,1,13,2];
- // 驚不驚喜,意不意外?
- a.sort(); // [1, 13, 2, 4]
這里就可以用reduce()來處理:
- // 這里是用 `>` 和`<` 比較日期對象,因此不會有問題
- const maxDate = dates.reduce((max, d) => d > max ? d : max, dates[0]);
4.分組計數(shù)
假設(shè)有個對象數(shù)組,每個對象上有個age屬性:
- const characters = [
- { name: 'Tom', age: 59 },
- { name: 'Jack', age: 29 },
- { name: 'Bruce', age: 29 }
- ];
怎樣返回一個對象,包含每個age的人物角色數(shù)量?比如這樣: { 29: 2, 59: 1 }.
用 reduce() 的實(shí)現(xiàn)方式如下:
- const reducer = (map, val) => {
- map[val] = map[val] || 1;
- ++map[val];
- return map;
- };
- characters.map(char => char.age).reduce(reducer, {});
5.Promise 動態(tài)鏈?zhǔn)秸{(diào)用
假設(shè)你有一個異步函數(shù)數(shù)組,想要按順序執(zhí)行:
- const functions = [
- async function() { return 1; },
- async function() { return 2; },
- async function() { return 3; }
- ];
如果是靜態(tài)的Promise代碼,我們直接在代碼里鏈?zhǔn)秸{(diào)用就行了。但如果是動態(tài)的Promise數(shù)組,可以用reduce串起來:
- // 最后 `res`的結(jié)果等價于`Promise.resolve().then(fn1).then(fn2).then(fn3)`
- const res = await functions.
- reduce((promise, fn) => promise.then(fn), Promise.resolve());
- res; // 3
當(dāng)然,reduce 能做的事情還有很多,它本質(zhì)上是對數(shù)組元素執(zhí)行某種“累進(jìn)”操作,最終返回單個值。
本文轉(zhuǎn)載自微信公眾號「1024譯站」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系1024譯站公眾號。