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