面試官直呼內(nèi)行!如何實現(xiàn)一個比較完美的reduce函數(shù)?
點擊上方?
程序員成長指北
,關注公眾號
回復 1 ,加入高級Node交流群
基本用法
reduce函數(shù)是js原生提供的用于處理數(shù)組結構的函數(shù)。
先來看一下MDN中的介紹:
reduce()?方法對數(shù)組中的每個元素按序執(zhí)行一個由用戶提供的?reducer?函數(shù),每一次運行?reducer?會將先前元素的計算結果作為參數(shù)傳入,最后將其結果匯總為單個返回值。
參數(shù)
-
callbackFn?一個?reducer?函數(shù),包含四個參數(shù):
-
previousValue:上一次調(diào)用?callbackFn時的返回值。在第一次調(diào)用時,若指定了初始值?initialValue,其值則為?initialValue,否則為數(shù)組索引為 0 的元素?array[0]。 -
currentValue:數(shù)組中正在處理的元素。在第一次調(diào)用時,若指定了初始值?initialValue,其值則為數(shù)組索引為 0 的元素?array[0],否則為?array[1]。 -
currentIndex:數(shù)組中正在處理的元素的索引。若指定了初始值?initialValue,則起始索引號為 0,否則從索引 1 起始。 -
array:用于遍歷的數(shù)組。
-
initialValue?可選 作為第一次調(diào)用?callback?函數(shù)時參數(shù)?previousValue?的值。若指定了初始值?initialValue,則?currentValue?則將使用數(shù)組第一個元素;否則?previousValue?將使用數(shù)組第一個元素,而?currentValue?將使用數(shù)組第二個元素。
reduce函數(shù)功能是非常強大的適用于非常多的場景:
比如使用reduce函數(shù)實現(xiàn)累加:
??let?total?=?[?0,?1,?2,?3?].reduce(
????(?previousValue,?currentValue?)?=>?previousValue?+?currentValue,
????0
??)
??//?6
生成新數(shù)組:
??let?total?=?[?0,?1,?2,?3?].reduce(
????function?(pre,?cur)?{
??????pre.push(cur?+?1)
??????return?pre
????},
????[]
??)
??//?[1,?2,?3,?4]
等等.....
那么問題來了,如何手寫實現(xiàn)一個reduce函數(shù)呢?
實現(xiàn)基礎版本
根據(jù)文檔可知,reduce函數(shù)接受一個運行函數(shù)和一個初始的默認值
???/**
????*
????*?@param?{Array}?data?原始數(shù)組
????*?@param?{Function}?iteratee?運行函數(shù)
????*?@param?{Any}?memo?初始值
????*?@returns?{boolean}?True?if?value?is?an?FormData,?otherwise?false
????*/
??function?myReduce(data,?iteratee,?memo)?{
??????//?...
??}
接下來實現(xiàn)基本功能
reduce函數(shù)的重點就是要將結果再次傳入執(zhí)行函數(shù)中進行處理
??function?myReduce(data,?iteratee,?memo)?{
???for(let?i?=?0;?i?<?data.length;?i++)?{
????memo?=?iteratee(memo,?data[i],?i,?data)
???}
???return?memo
??}
需求一:增加this綁定
其實reduce函數(shù)可以指定自定義對象綁定this
在這里可以使用call對函數(shù)進行重新綁定
??function?myReduce(data,?iteratee,?memo,?context)?{
???//?重置iteratee函數(shù)的this指向
???iteratee?=?bind(iteratee,?context)
??
???for(let?i?=?0;?i?<?data.length;?i++)?{
????memo?=?iteratee(memo,?data[i],?i,?data)
???}
???return?memo
??}
??
??//?綁定函數(shù)?使用call進行綁定
??function?bind(fn,?context)?{
????//?返回一個匿名函數(shù),執(zhí)行時重置this指向
????return?function(memo,?value,?index,?collection)?{
??????return?fn.call(context,?memo,?value,?index,?collection);
????};
??}
??
需求二:增加對第二個參數(shù)默認值的支持
reduce函數(shù)的第三個參數(shù)也是可選值,如果沒有傳遞第三個參數(shù),那么直接使用傳入數(shù)據(jù)的第一個位置初始化
??function?myReduce(data,?iteratee,?memo,?context)?{
???//?重置iteratee函數(shù)的this指向
???iteratee?=?bind(iteratee,?context)
???//?判斷是否傳遞了第三個參數(shù)
???let?initial??=?arguments.length?>=?3;?//?新增
???//?初始的遍歷下標
???let?index?=?0?//?新增
??
???if(!initial)?{?//?新增
????//?如果用戶沒有傳入默認值,那么就取數(shù)據(jù)的第一項作為默認值
????memo?=?data[index]?//?新增
????//?所以遍歷就要從第二項開始
????index?+=?1?//?新增
???}
??
???for(let?i?=?index;?i?<?data.length;?i++)?{?//?修改
????memo?=?iteratee(memo,?data[i],?i,?data)
???}
???return?memo
??}
??
??//?綁定函數(shù)?使用call進行綁定
??function?bind(fn,?context)?{
????return?function(memo,?value,?index,?collection)?{
??????return?fn.call(context,?memo,?value,?index,?collection);
????};
??}
??
需求三:支持對象
js原生的reduce函數(shù)是不支持對象這種數(shù)據(jù)結構的,那么如何完善我們的reduce函數(shù)呢?
其實只需要取出對象中所有的key,然后遍歷key就可以了
?function?myReduce(data,?iteratee,?memo,?context)?{
?
???iteratee?=?bind(iteratee,?context)
???//?取出所有的key值
???let?_keys?=?!Array.isArray(data)?&&?Object.keys(data)?//?新增
???//?長度賦值
???let?len?=?(_keys?||?data).length?//?新增
?
???let?initial??=?arguments.length?>=?3;
???let?index?=?0
??
???if(!initial)?{
????//?如果沒有設置默認值初始值,那么取第一個值的操作也要區(qū)分對象/數(shù)組
????memo?=?data[?_keys???_keys[index]?:?index]?//?修改
????index?+=?1
???}
??
???for(let?i?=?index;?i?<?len;?i++)?{
????//?取key值
????let?currentKey?=?_keys???_keys[i]?:?i?//?新增
????memo?=?iteratee(memo,?data[currentKey],?currentKey,?data)?//?修改
???}
???return?memo
??}
??
??function?bind(fn,?context)?{
????return?function(memo,?value,?index,?collection)?{
??????return?fn.call(context,?memo,?value,?index,?collection);
????};
??}
??
需求四:reduceRight
其實以上的內(nèi)容已經(jīng)是一個比較完整的reduce函數(shù)了,最后一個擴展內(nèi)容是reduceRight函數(shù),其實reduceRight函數(shù)的功能也很簡單,就是在遍歷的時候倒序進行操作,例如:
?let?total?=?[?0,?1,?2,?3?].reduce(
????function?(pre,?cur)?{
??????pre.push(cur?+?1)
??????return?pre
????},
????[]
??)
??
??//?[1,?2,?3,?4]
其實實現(xiàn)這個功能也非常簡單,只需要初始操作的值更改為最后一個元素的位置就可以了:
//?加入一個參數(shù)dir,用于標識正序/倒序遍歷
function?myReduce(data,?iteratee,?memo,?context,?dir)?{?//修改
??iteratee?=?bind(iteratee,?context)
??let?_keys?=?!Array.isArray(data)?&&?Object.keys(data)
??let?len?=?(_keys?||?data).length
??let?initial??=?arguments.length?>=?3;
??//?定義下標
??let?index?=?dir?>?0???0?:?len?-?1?//?修改
?
??if(!initial)?{
???memo?=?data[?_keys???_keys[index]?:?index]
???//?定義初始值
???index?+=?dir?//?修改
??}
??//?每次修改只需步進指定的值
??for(;index?>=?0?&&?index?<?len;?index?+=?dir)?{?//?修改
???let?currentKey?=?_keys???_keys[index]?:?index
???memo?=?iteratee(memo,?data[currentKey],?currentKey,?data)
??}
??return?memo
?}
?
?function?bind(fn,?context)?{
???if?(!context)?return?fn;
???return?function(memo,?value,?index,?collection)?{
?????return?fn.call(context,?memo,?value,?index,?collection);
???};
?}
調(diào)用的時候直接傳入最后一個參數(shù)為1 / \-1即可
?myReduce([1,?2,?3,?4],?function(pre,?cur)?{
?????console.log(cur)
?},?[],?1)
?
?myReduce([1,?2,?3,?4],?function(pre,?cur)?{
?????console.log(cur)
?},?[],?-1)
最后將整個函數(shù)進行重構抽離成為一個單獨的函數(shù):
?function?createReduce(dir)?{
?????function?reduce()?{
???//?....
?????}
?
?????return?function()?{
???return?reduce()
?????}
?}
最后最終的代碼如下:
?function?createReduce(dir)?{
?
?????function?reduce(data,?fn,?memo,?initial)?{
?????????let?_keys?=?Array.isArray(data)?&&?Object.keys(data),
?????????????len?=?(_keys?||?data).length,
?????????????index?=?dir?>?0???0?:?len?-?1;
?????????if?(!initial)?{
?????????????memo?=?data[_keys???_keys[index]?:?index]
?????????????index?+=?dir
?????????}
?????????for?(;?index?>=?0?&&?index?<?len;?index?+=?dir)?{
?????????????let?currentKey?=?_keys???_keys[index]?:?index
?????????????memo?=?fn(memo,?data[currentKey],?currentKey,?data)
?????????}
?????????return?memo
?????}
?????return?function?(data,?fn,?memo,?context)?{
?????????let?initial?=?arguments.length?>=?3
?????????return?reduce(data,?bind(fn,?context),?memo,?initial)
?????}
?}
?function?bind(fn,?context)?{
?????if?(!context)?return?fn;
?????return?function?(memo,?value,?index,?collection)?{
?????????return?fn.call(context,?memo,?value,?index,?collection);
?????};
?}
?let?reduce?=?createReduce(1)
?let?reduceRight?=?createReduce(-1)
而這種實現(xiàn)方式也是underscore.js所實現(xiàn)的reduce函數(shù)的方式。
寫在最后??
未來可能會更新實現(xiàn)mini-vue3和javascript基礎知識系列,希望能一直堅持下去,期待多多點贊????,一起進步!????
關于本文
來自:pino
https://juejin.cn/post/7113743909452251167
Node 社群
我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學習感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關的交流、學習、共建。下方加 考拉 好友回復「Node」即可。
如果你覺得這篇內(nèi)容對你有幫助,我想請你幫我2個小忙:
點贊和在看就是最大的支持 ??
