溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

面試官:你為什么這么強,什么都敢盤(reduce)

發(fā)布時間:2020-06-29 06:32:49 來源:網(wǎng)絡(luò) 閱讀:222 作者:可樂程序員 欄目:web開發(fā)

1. 用reduce實現(xiàn)數(shù)組一些api

給數(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ù)制代碼

2. 不是數(shù)組怎么reduce

上面的測試也用了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ù)制代碼

3. compose思想

對于數(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盤它就是了

【廣告】ts類型注解生成器

多使用幾次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ù)制代碼

面試官:你為什么這么強,什么都敢盤(reduce)


向AI問一下細(xì)節(jié)

免責(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)容。

AI