JavaScript Reduce使用詳解
學(xué)會(huì)這一個(gè)技巧 Reduce
讓你開啟編程新世界
Learning This Reduce
Skill and a Whole New World Will Open up for You 🎉
reduce
可謂是 JS 數(shù)組方法最靈活的一個(gè),因?yàn)榭梢蕴娲鷶?shù)組的其他方法,比如 map
/ filter
/ some
/ every
等,也是最難理解的一個(gè)方法,lodash 很多方法也可以用其實(shí)現(xiàn),學(xué)會(huì) reduce 將給與開發(fā)者另一種函數(shù)式(Functional)、聲明式(Declarative)的視角解決問題,而不是以往的過程式(Procedual)或命令式(Imperative)
其中一個(gè)難點(diǎn)在于判斷 acc
即 accumulation
的類型以及如何選擇初始值,其實(shí)有個(gè)小技巧,可以幫助我們找到合適的初始值,我們想要的返回值的類型和 acc
類型需要是一樣的,比如求和最終結(jié)果是數(shù)字,則 acc
應(yīng)該是數(shù)字類型,故其初始化必定是 0
。
下面開始鞏固對(duì) reduce
的理解和用法。
map
根據(jù)小技巧,map
最終返回值是數(shù)組,故 acc
也應(yīng)該是一個(gè)數(shù)組,初始值使用空數(shù)組即可。
/** * Use `reduce` to implement the builtin `Array.prototype.map` method. * @param {any[]} arr * @param {(val: any, index: number, thisArray: any[]) => any} mapping * @returns {any[]} */ function map(arr, mapping) { return arr.reduce((acc, item, index) => [...acc, mapping(item, index, arr)], []); }
測試
map([null, false, 1, 0, '', () => {}, NaN], val => !!val); // [false, false, true, false, false, true, false]
filter
根據(jù)小技巧,filter
最終返回值也是數(shù)組,故 acc
也應(yīng)該是一個(gè)數(shù)組,使用空數(shù)組即可。
/** * Use `reduce` to implement the builtin `Array.prototype.filter` method. * @param {any[]} arr * @param {(val: any, index: number, thisArray: any[]) => boolean} predicate * @returns {any[]} */ function filter(arr, predicate) { return arr.reduce((acc, item, index) => predicate(item, index, arr) ? [...acc, item] : acc, []); }
測試
filter([null, false, 1, 0, '', () => {}, NaN], val => !!val); // [1, () => {}]
some
some
當(dāng)目標(biāo)數(shù)組為空返回 false
,故初始值為 false
。
function some(arr, predicate) { return arr.reduce((acc, val, idx) => acc || predicate(val, idx, arr), false) }
測試:
some([null, false, 1, 0, '', () => {}, NaN], val => !!val); // true some([null, false, 0, '', NaN], val => !!val); // false
附帶提醒,二者對(duì)結(jié)果沒影響但有性能區(qū)別,acc 放到前面因?yàn)槭嵌搪匪惴?,可避免無謂的計(jì)算,故性能更高。
acc || predicate(val, idx, arr)
和
predicate(val, idx, arr) || acc
every
every
目標(biāo)數(shù)組為空則返回 true,故初始值為 true
function every(arr, predicate) { return arr.reduce((acc, val, idx) => acc && predicate(val, idx, arr), true) }
findIndex
findIndex
目標(biāo)數(shù)組為空返回 -1,故初始值 -1。
function findIndex(arr, predicate) { const NOT_FOUND_INDEX = -1; return arr.reduce((acc, val, idx) => { if (acc === NOT_FOUND_INDEX) { return predicate(val, idx, arr) ? idx : NOT_FOUND_INDEX; } return acc; }, NOT_FOUND_INDEX) }
測試
findIndex([5, 12, 8, 130, 44], (element) => element > 8) // 3
pipe
一、實(shí)現(xiàn)以下函數(shù)
/** * Return a function to make the input value processed by the provided functions in sequence from left the right. * @param {(funcs: any[]) => any} funcs * @returns {(arg: any) => any} */ function pipe(...funcs) {}
使得
pipe(val => val * 2, Math.sqrt, val => val + 10)(2) // 12
利用該函數(shù)可以實(shí)現(xiàn)一些比較復(fù)雜的處理過程
// 挑選出 val 是正數(shù)的項(xiàng)對(duì)其 val 乘以 0.1 系數(shù),然后將所有項(xiàng)的 val 相加,最終得到 3 const process = pipe( arr => arr.filter(({ val }) => val > 0), arr => arr.map(item => ({ ...item, val: item.val * 0.1 })), arr => arr.reduce((acc, { val }) => acc + val, 0) ); process([{ val: -10 }, { val: 20 }, { val: -0.1 }, { val: 10 }]) // 3
二、實(shí)現(xiàn)以下函數(shù),既能實(shí)現(xiàn)上述 pipe 的功能,而且返回函數(shù)接納參數(shù)個(gè)數(shù)可不定
/** * Return a function to make the input values processed by the provided functions in sequence from left the right. * @param {(funcs: any[]) => any} funcs * @returns {(args: any[]) => any} */ function pipe(...funcs) {}
使得以下單測通過
pipe(sum, Math.sqrt, val => val + 10)(0.1, 0.2, 0.7, 3) // 12
其中 sum
已實(shí)現(xiàn)
/** * Sum up the numbers. * @param args number[] * @returns {number} the total sum. */ function sum(...args) { return args.reduce((a, b) => a + b); }
參考答案
一、返回函數(shù)接受一個(gè)參數(shù)
省略過濾掉非函數(shù)的 func 步驟
/** * Return a function to make the input value processed by the provided functions in sequence from left the right. * @param {(arg: any) => any} funcs * @returns {(arg: any) => any} */ function pipe(...funcs) { return (arg) => { return funcs.reduce( (acc, func) => func(acc), arg ) } }
二、返回函數(shù)接受不定參數(shù)
同樣省略了過濾掉非函數(shù)的 func 步驟
/** * Return a function to make the input value processed by the provided functions in sequence from left the right. * @param {Array<(...args: any) => any>} funcs * @returns {(...args: any[]) => any} */ function pipe(...funcs) { // const realFuncs = funcs.filter(isFunction); return (...args) => { return funcs.reduce( (acc, func, idx) => idx === 0 ? func(...acc) : func(acc), args ) } }
性能更好的寫法,避免無謂的對(duì)比,浪費(fèi) CPU
function pipe(...funcs) { return (...args) => { // 第一個(gè)已經(jīng)處理,只需處理剩余的 return funcs.slice(1).reduce( (acc, func) => func(acc), // 首先將特殊情況處理掉當(dāng)做 `acc` funcs[0](...args) ) } }
第二種寫法的 funcs[0](...args)
這個(gè)坑要注意,數(shù)組為空就爆炸了,因?yàn)榭罩羔樍恕?/p>
實(shí)現(xiàn) lodash.get
實(shí)現(xiàn) get
使得以下示例返回 'hello world'
。
const obj = { a: { b: { c: 'hello world' } } }; get(obj, 'a.b.c');
函數(shù)簽名:
/** * pluck the value by key path * @param any object * @param keyPath string 點(diǎn)分隔的 key 路徑 * @returns {any} 目標(biāo)值 */ function get(obj, keyPath) {}
參考答案
/** * Pluck the value by key path. * @param any object * @param keyPath string 點(diǎn)分隔的 key 路徑 * @returns {any} 目標(biāo)值 */ function get(obj, keyPath) { if (!obj) { return undefined; } return keyPath.split('.').reduce((acc, key) => acc[key], obj); }
實(shí)現(xiàn) lodash.flattenDeep
雖然使用 concat 和擴(kuò)展運(yùn)算符只能夠 flatten 一層,但通過遞歸可以去做到深度 flatten。
方法一:擴(kuò)展運(yùn)算符
function flatDeep(arr) { return arr.reduce((acc, item) => Array.isArray(item) ? [...acc, ...flatDeep(item)] : [...acc, item], [] ) }
方法二:concat
function flatDeep(arr) { return arr.reduce((acc, item) => acc.concat(Array.isArray(item) ? flatDeep(item) : item), [] ) }
有趣的性能對(duì)比,擴(kuò)展操作符 7 萬次 1098ms,同樣的時(shí)間 concat 只能執(zhí)行 2 萬次
function flatDeep(arr) { return arr.reduce((acc, item) => Array.isArray(item) ? [...acc, ...flatDeep(item)] : [...acc, item], [] ) } var arr = repeat([1, [2], [[3]], [[[4]]]], 20); console.log(arr); console.log(flatDeep(arr)); console.time('concat') for (i = 0; i < 7 * 10000; ++i) { flatDeep(arr) } console.timeEnd('concat') function repeat(arr, times) { let result = []; for (i = 0; i < times; ++i) { result.push(...arr) } return result; }
過濾掉對(duì)象中的空值
實(shí)現(xiàn)
clean({ foo: null, bar: undefined, baz: 'hello' }) // { baz: 'hello' }
答案
/** * Filter out the `nil` (null or undefined) values. * @param {object} obj * @returns {any} * * @example clean({ foo: null, bar: undefined, baz: 'hello' }) * * // => { baz: 'hello' } */ export function clean(obj) { if (!obj) { return obj; } return Object.keys(obj).reduce((acc, key) => { if (!isNil(obj[key])) { acc[key] = obj[key]; } return acc; }, {}); }
enumify
將常量對(duì)象模擬成 TS 的枚舉
實(shí)現(xiàn) enumify
使得
const Direction = { UP: 0, DOWN: 1, LEFT: 2, RIGHT: 3, }; const actual = enumify(Direction); const expected = { UP: 0, DOWN: 1, LEFT: 2, RIGHT: 3, 0: 'UP', 1: 'DOWN', 2: 'LEFT', 3: 'RIGHT', }; deepStrictEqual(actual, expected);
答案:
/** * Generate enum from object. * @see https://www.typescriptlang.org/play?#code/KYOwrgtgBAglDeAoKUBOwAmUC8UCMANMmpgEw5SlEC+UiiAxgPYgDOTANsAHQdMDmAChjd0GAJQBuRi3ZdeA4QG08AXSmIgA * @param {object} obj * @returns {object} */ export function enumify(obj) { if (!isPlainObject(obj)) { throw new TypeError('the enumify target must be a plain object'); } return Object.keys(obj).reduce((acc, key) => { acc[key] = obj[key]; acc[obj[key]] = key; return acc; }, {}); }
Promise 串行執(zhí)行器
利用 reduce 我們可以讓不定數(shù)量的 promises 串行執(zhí)行,在實(shí)際項(xiàng)目中能發(fā)揮很大作用。此處不細(xì)講,請(qǐng)參考我的下一篇文章 JS 請(qǐng)求調(diào)度器。
拓展
請(qǐng)使用 jest 作為測試框架,給本文的所有方法書寫單測
更多習(xí)題見 github.com/you-dont-ne…
以上就是JavaScript Reduce使用詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript Reduce使用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript中時(shí)間日期函數(shù)new?Date()詳解(5種獲取時(shí)間戳的函數(shù))
這篇文章主要給大家介紹了關(guān)于JavaScript中時(shí)間日期函數(shù)new?Date()的相關(guān)資料,主要講的是JS中5種獲取時(shí)間戳的函數(shù),new Date()是JavaScript中用于獲取當(dāng)前日期和時(shí)間的內(nèi)置函數(shù),需要的朋友可以參考下2024-04-04JS用斜率判斷鼠標(biāo)進(jìn)入DIV四個(gè)方向的方法
在網(wǎng)上去搜判斷鼠標(biāo)移入div移入移出的方法大同小異,下面小編給大家分享一篇文章關(guān)于js判斷鼠標(biāo)進(jìn)入div方向的代碼,感興趣的朋友一起看看吧2016-11-11js數(shù)組常用19種方法(你會(huì)的到底有多少呢)
這篇文章主要給大家介紹了關(guān)于js數(shù)組常用19種方法,大家可以看看你會(huì)的到底有多少呢,在日常開發(fā)中我們會(huì)接觸到j(luò)s中數(shù)組的一些方法,需要的朋友可以參考下2023-09-09JS不同地圖坐標(biāo)系經(jīng)緯度轉(zhuǎn)換方法(天地圖、高德地圖、百度地圖、騰訊地圖)
這篇文章主要給大家介紹了關(guān)于JS不同地圖坐標(biāo)系經(jīng)緯度轉(zhuǎn)換的相關(guān)資料,分別包括天地圖、高德地圖、百度地圖、騰訊地圖,需要的朋友可以參考下2024-07-07詳解微信小程序(Taro)手動(dòng)埋點(diǎn)和自動(dòng)埋點(diǎn)的實(shí)現(xiàn)
這篇文章主要介紹了詳解微信小程序(Taro)手動(dòng)埋點(diǎn)和自動(dòng)埋點(diǎn)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03Echarts直角坐標(biāo)系x軸y軸屬性設(shè)置整理大全
直角坐標(biāo)系的設(shè)置指的是網(wǎng)格,坐標(biāo)軸和區(qū)域縮放的配置,下面這篇文章主要給大家介紹了關(guān)于Echarts直角坐標(biāo)系x軸y軸屬性設(shè)置的相關(guān)資料,需要的朋友可以參考下2022-11-11js合并數(shù)組對(duì)象代碼實(shí)現(xiàn)(將數(shù)組中具有相同屬性對(duì)象合并到一起組成一個(gè)新數(shù)組)
項(xiàng)目過程中經(jīng)常會(huì)遇到JS數(shù)組合并的情況,時(shí)常為這個(gè)糾結(jié),這篇文章主要給大家介紹了關(guān)于js合并數(shù)組對(duì)象(將數(shù)組中具有相同屬性對(duì)象合并到一起組成一個(gè)新數(shù)組)的相關(guān)資料,需要的朋友可以參考下2024-01-01