您好,登錄后才能下訂單哦!
給數(shù)組prototype加上基于reduce實現(xiàn)的api:
Object.assign(Array.prototype,?{ ?myMap(cb,?_this?=?this)?{?return?this.reduce((res,?cur,?index,?array)?=>?[...res,?cb.call(_this,?cur,?index,?array)],?[]); ?}, ?myFind(cb,?_this?=?this)?{?return?this.reduce((res,?cur,?index,?array)?=>?res?||?(cb.call(_this,?cur,?index,?array)???cur?:?undefined),?undefined) ?}, ?myFilter(cb,?_this?=?this)?{?return?this.reduce((res,?cur,?index,?array)?=>?[...res,?...(cb.call(_this,?cur,?index,?array)???[cur]?:?[])],?[]); ?}, ?myEvery(cb,?_this?=?this)?{?return?this.reduce((res,?cur,?index,?array)?=>?res?&&?!!cb.call(_this,?cur,?index,?array),?true); ?}, ?mySome(cb,?_this?=?this)?{?return?this.reduce((res,?cur,?index,?array)?=>?res?||?!!cb.call(_this,?cur,?index,?array),?false); ?}, }); 復(fù)制代碼
接下來寫測試用例:
//?函數(shù)用例const?tests?=?{ ?map:?[?item?=>?item?*?2,?function(_,?index)?{?return?this[index]?}?//?這this是專門測cb傳入第二個參數(shù)使用的 ?], ?find:?[?item?=>?item,?item?=>?item?===?6,?item?=>?item?===?Symbol(),?function(_,?index)?{?return?this[index]?===?6?} ?], ?filter:?[?item?=>?item?>?6,?item?=>?item,?function(_,?index)?{?return?this[index]?>?6?} ?], ?every:?[?item?=>?item,?item?=>?item?>?6,?function(_,?index)?{?return?this[index]?>?6?} ?], ?some:?[?item?=>?item,?item?=>?item?>?6,?function(_,?index)?{?return?this[index]?>?6?} ?], }//?數(shù)據(jù)源const?example?=?[ ?[1,2,3,4,5,6,7], ?[1,2,3,4,5], ?[11,12,13,14,15], ]; 復(fù)制代碼
測試用例考慮普通情況以及第二個改變this的參數(shù)的情況,最后需要一個用例執(zhí)行的方法:
//?簡單的比較相等function?isEqual(a,?b)?{?if?(typeof?a?!==?'object'?&&?typeof?b?!==?'object')?{?return?a?===?b ?}?//?這是測試[1,?2,?3]和[1,?2,?3]用的 ?//?本文只有number和number[]沒有其他數(shù)據(jù)結(jié)構(gòu) ?return?`${a}`?===?`$`; }function?doTest(example,?tests)?{?//?以數(shù)據(jù)源為key,數(shù)組的isEqual是通過隱式轉(zhuǎn)換比較 ?return?example.reduce((res,?cur)?=>?{?//?對函數(shù)用例逐個執(zhí)行,把有沒有相等的true和false寫進(jìn)去 ?res[cur]?=?Object.entries(tests).reduce((result,?[key,?fns])?=>?{ ?result[key]?=?fns.map(fn?=> ?example.map(eg?=> ?isEqual( ?eg[key](fn,?[5,?6,?7]), ?eg[`my${key[0].toUpperCase()}${key.slice(1)}`](fn,?[5,?6,?7]) ?) ?));?return?result; ?},?{});?return?res; ?},?{}); } doTest(example,?tests)//?如果全部都是true,說明測試通過復(fù)制代碼
上面的測試也用了reduce,是對一個對象reduce。只要是遍歷某個數(shù)據(jù)結(jié)構(gòu),產(chǎn)生一個結(jié)果,那么都可以使用reduce解決:
普通對象:使用Object.keys,Object.values,Object.entries再reduce
類數(shù)組對象:使用[...o]
字符串: [].reduce.call(string, (res, cur) => {}, result)
假數(shù)組: 如{ 0: 'a', 1: 'b', length: 2 },使用Array.from(o)、Array.apply(null, o)
有symbol做key的對象:使用getOwnPropertySymbols
下面先來幾個最簡單的例子,希望平時基本沒用reduce的人,可以通過幾個例子找到一點reduce的感覺。reduce可以簡化代碼,讓思路更加清晰,而不是被for循環(huán)的下標(biāo)迷惑了自己
根據(jù)對象生成一個簡單schema:
//?value值變成對應(yīng)的type,如果是對象,則遞歸下一級function?transformSchema(o)?{?return?Object.entries(o).reduce((res,?[key,?value])?=>?{ ?res[key]?=?typeof?value?!==?'object'???typeof?value?:?transformSchema(value);?return?res; ?},?Array.isArray(o)???[]?:?{}); } transformSchema({?a:?1,?b:?'2',?c:?{?d:?1,?e:?[{a:?1,?b:2}]}?}) 復(fù)制代碼
統(tǒng)計頁面上a標(biāo)簽的個數(shù)
[...document.querySelectorAll('*')] ?.reduce((sum,?node)?=>?node.nodeName?===?'A'???sum?:?sum?+?1,?0) 復(fù)制代碼
統(tǒng)計字符串每一個字符出現(xiàn)次數(shù):
;[].reduce.call('asfsdhvui3u2498rfrvh?93c?293ur0jvdf',?(res,?cur)?=>?{ ?res[cur]?=?res[cur]?||?0; ?res[cur]?++;?return?res; },?{}) 復(fù)制代碼
扁平化數(shù)組(不用flat和join)
function?flattern(arr)?{?return?arr.reduce((res,?cur)?=>? ?res.concat(Array.isArray(cur)???flattern(cur)?:?[cur]), ?[]); } 復(fù)制代碼
數(shù)組去重,兼容各種類型,比較完美的版本:
function?isNotSimple(o)?{?return?Object.prototype.toString.call(o)?===?'[object?Object]'?||?Array.isArray(o)?||?typeof?o?===?'function'}function?deepEqual(a?=?{},?b?=?{},?cache?=?new?Set())?{?if?(typeof?a?===?'function')?{?//?函數(shù)的情況 ?return?a.toString()?===?b.toString() ?}?if?(cache.has(a))?{?//?解決環(huán)引用 ?return?a?===?b ?} ?cache.add(a)?const?keys?=?Object.keys(a)?const?symbolKeys?=?Object.getOwnPropertySymbols(a)?//?考慮symbol做key ?return?(keys.length?===?Object.keys(b).length?&& ?symbolKeys.length?===?Object.getOwnPropertySymbols(b).length)?&& ?[...keys,?...symbolKeys].every(key?=>?!isNotSimple(a[key])?? ?a[key]?===?b[key]?:?deepEqual(a[key],?b[key],?cache)) }function?unique(arr)?{?const?cache?=?new?Set()?//?set可以干掉NaN ?const?objCache?=?[]?//?簡單的基本類型直接來,復(fù)雜的使用deepEqual ?return?arr.reduce((res,?cur)?=>?( ?!isNotSimple(cur)???!cache.has(cur)?&&?res.push(cur)?&&?cache.add(cur) ?:?!objCache.find(o?=>?deepEqual(o,?cur))?&&?objCache.push(cur)?&&?res.push(cur), ?res ?),?[]); } 復(fù)制代碼
將傳入的所有參數(shù)生成一個單鏈表:
function?createLinkList(...init)?{ ?let?current?return?init.reduce((res,?cur)?=>?{ ?current?=?current?||?res ?current.value?=?cur ?current.next?=?current.next?||?{} ?current?=?current.next?return?res ?},?{}) } createLinkList(1,2,4,5,6); 復(fù)制代碼
創(chuàng)建一個樹形結(jié)構(gòu):
const?ran?=?()?=>?~~(Math.random()?*?2)?+?1function?createTree(dept?=?0)?{?if?(dept?>?1)?{?return?null; ?}?//?如果每一層是數(shù)組型的樹結(jié)構(gòu),用map也可以 ?//?reduce還可以兼容非數(shù)組的結(jié)構(gòu),還可以完成其他更復(fù)雜的需求 ?return?Array.apply(null,?{?length:?ran()?}).reduce((res,?cur,?i)?=>?{ ?res[i]?=?{?value:?ran(),?nodes:?createTree(dept?+?1), ?}?return?res; ?},?{}); }const?tree?=?createTree(); 復(fù)制代碼
基于上面的樹結(jié)構(gòu),找出某個節(jié)點值的出現(xiàn)次數(shù):
//?如果當(dāng)前節(jié)點值等于target,則+1;如果有子節(jié)點,則帶上sum遞歸計算 function?targetFromTree(tree?=?{},?target,?sum?=?0)?{ ?return?Object.values(tree).reduce((res,?node)?=>? ?res?+?~~(node.value?===?target)?+?targetFromTree(node.nodes,?target,?sum) ?,?sum); } 復(fù)制代碼
對于數(shù)組api,經(jīng)常有鏈?zhǔn)讲僮?,?
[1,2,3,4,5].filter(x?=>?x?>?3).map(x?=>?x?*?2) 復(fù)制代碼
這樣子,對每一個元素filter一下,遍歷一次。對每一個元素map,再遍歷一次。其實這一切我們可以做到只遍歷一次就完成兩個操作,遍歷的時候?qū)γ恳粋€元素做所有的函數(shù)復(fù)合起來的一個總函數(shù)的操作
class?MagicArray?extends?Array?{ ?temp?=?[];?//?存放鏈?zhǔn)讲僮鞯姆椒??FLAG?=?Symbol();?//?filter標(biāo)記 ?//?如果有filter標(biāo)記則直接返回 ?myMap(cb,?_this?=?this)?{?this.temp.push((cur,?index,?array)?=>?cur?===?this.FLAG???this.FLAG?:?cb.call(_this,?cur,?index,?array));?return?this; ?}?//?不符合要求的打上filter標(biāo)記 ?myFilter(cb,?_this?=?this)?{?this.temp.push((cur,?index,?array)?=>?cb.call(_this,?cur,?index,?array)???cur?:?this.FLAG);?return?this; ?} ?run()?{?//?函數(shù)compose ?const?f?=?this.temp.reduceRight((a,?b)?=>?(cur,?...rest)?=>?a(b(cur,?...rest),?...rest)); ?const?result?=?this.reduce((res,?cur,?index,?arr)?=>?{ ?const?ret?=?f(cur,?index,?arr);?//?filter標(biāo)記的元素直接跳過 ?if?(ret?===?this.FLAG)?{?return?res; ?} ?res.push(ret);?return?res; ?},?[]);?this.temp?=?[];?return?result; ?} } 復(fù)制代碼
我們已經(jīng)完成了一個具有magic的數(shù)組,接下來測試一下和原生操作誰快:
const?a?=?new?MagicArray(...Array.apply(null,?{?length:?10000?}).map(x?=>?Math.random()?*?10));console.time('normal') a.map(x?=>?x?*?2).filter(x?=>?x?>?5)console.timeEnd('normal')console.time('compose') a.myMap(x?=>?x?*?2).myFilter(x?=>?x?>?5).run()console.timeEnd('compose') 復(fù)制代碼
經(jīng)過多次測試,compose過的數(shù)組與常規(guī)數(shù)組耗時比約為3:5
對于this.temp.reduceRight((a, b) => (cur, ...rest) => a(b(cur, ...rest), ...rest));這段代碼怎么理解?
類似于各種框架的中間件的實現(xiàn),我們這里的實現(xiàn)是傳入?yún)?shù)和數(shù)組的item, index, array一致,但是我們這里的item是上一次的運行結(jié)果,故有b(cur, ...rest), ...rest)的操作
總之,遇到遍歷一個數(shù)據(jù)結(jié)構(gòu)最后生成一個或多個結(jié)果(多個結(jié)果res用一個對象多個屬性表示)的情況,那就用reduce盤它就是了
多使用幾次reduce,就會發(fā)現(xiàn)它帶來更好的開發(fā)體驗和提高效率,也是造輪子用的比較多的。最近寫了一個小工具,將已知的json結(jié)構(gòu)轉(zhuǎn)成ts聲明。在源碼里面,可以感受一下用了reduce后,遞歸、遍歷邏輯一切都十分明朗。
//?已知json{?"a":?1,?"b":?"1",?"c":?{?"d":?1,?"e":?[?"1", ?{?"g":?1,?"r":?"asd",?"gg":?true ?},?1 ?] ?} }//?轉(zhuǎn)換結(jié)果{ ?a:?number; ?b:?string; ?c:?{ ?d:?number; ?e:?number?|?{ ?g:?number; ?r:?string; ?gg:?boolean; ?}?|?string?[]; ?}; } 復(fù)制代碼
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。