哈嘍,我是老魚(yú)!
最近對(duì)一個(gè)工具庫(kù)的使用上癮了!這個(gè)給大家分享下。這是每個(gè) JavaScript 程序員都應(yīng)該掌握的工具:Ramda
簡(jiǎn)介
Ramda
是一款實(shí)用的 JavaScript 函數(shù)式編程庫(kù)懊亡。類(lèi)似的庫(kù)中,大家最為熟悉的有Underscore蜕提、 Lodash等忧风。
這時(shí)大家可能會(huì)問(wèn):
既然 Underscore 和 Lodash 已經(jīng)這么流行了,為什么還要學(xué)習(xí)好像雷同的 Ramda 呢融痛?
回答是壶笼,Ramda 強(qiáng)調(diào)更加純粹的函數(shù)式風(fēng)格。數(shù)據(jù)不變性和函數(shù)無(wú)副作用是其核心設(shè)計(jì)理念雁刷。這可以幫助你使用簡(jiǎn)潔覆劈、優(yōu)雅的代碼來(lái)完成工作。
Ramda 的目標(biāo)更為專(zhuān)注:專(zhuān)門(mén)為函數(shù)式編程風(fēng)格而設(shè)計(jì),更容易創(chuàng)建函數(shù)式 pipeline责语、且從不改變用戶(hù)已有數(shù)據(jù)炮障。
對(duì)比區(qū)分
Underscore 和 Lodash的參數(shù)位置不對(duì)
,把處理的數(shù)據(jù)放到了第一個(gè)參數(shù)坤候。
var?square?=?n?=>?n?*?n;
_.map([4,?8],?square)?//?[16,?64]
上面代碼中胁赢,_.map的第一個(gè)參數(shù)[4, 8]是要處理的數(shù)據(jù),第二個(gè)參數(shù)square是數(shù)據(jù)要執(zhí)行的運(yùn)算白筹。
Ramda 的數(shù)據(jù)一律放在最后一個(gè)參數(shù)智末,理念是"function first,data last"
遍蟋。
Ramda應(yīng)該是目前最符合函數(shù)式編程的工具庫(kù)吹害,它需要操作的數(shù)據(jù)參數(shù)都是放在最后的螟凭。
var?R?=?require('ramda');
R.map(square,?[4,?8])?//?[16,?64]
把要操作的數(shù)據(jù)放在最后一個(gè)參數(shù)虚青,作為參數(shù)排列順序,不僅滿(mǎn)足 Ramda 的核心設(shè)計(jì)螺男,同時(shí)使得所有方法都支持柯里化
棒厘。
安裝
使用 node:
$?npm?install?ramda
或從 CDN 上獲取:
<script?src="http://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.0/ramda.min.js"></script>
然后即可使用:
import?*?as?R?from?'ramda'
//?OR
const?R?=?require('ramda');
也就是說(shuō)下隧,所有多參數(shù)的函數(shù)奢人,默認(rèn)都可以單參數(shù)使用。也可以讓你在只提供部分參數(shù)的情況下淆院,輕松地在已有函數(shù)的基礎(chǔ)上創(chuàng)建新函數(shù)何乎。
//?寫(xiě)法一
R.map(square,?[4,?8])
//?寫(xiě)法二
R.map(square)([4,?8])
//?或者
var?mapSquare?=?R.map(square);
mapSquare([4,?8]);
上面代碼中,寫(xiě)法一是多參數(shù)版本土辩,寫(xiě)法二是柯里化以后的單參數(shù)版本支救。Ramda 都支持,并且推薦使用第二種寫(xiě)法拷淘。
今天各墨,接下來(lái)是我總結(jié)的Ramda的幾種常見(jiàn)的使用場(chǎng)景,展示怎樣用 Ramda 寫(xiě)出既簡(jiǎn)潔易讀启涯,又方便擴(kuò)展復(fù)用的代碼贬堵。
使用集合迭代函數(shù)代替循環(huán)
.forEach
不必寫(xiě)顯式for循環(huán),而是用 forEach 函數(shù)代替循環(huán)结洼。示例如下:
//?Replace?this:
for?(const?value?of?myArray)?{
??console.log(value+5)
}
?
//?with:
const?printXPlusFive?=?x?=>?console.log(x?+?5);
R.forEach(printXPlusFive,?[1,?2,?3]);?//=>?[1,?2,?3]
//?logs?6
//?logs?7
//?logs?8
forEach 接受一個(gè)函數(shù)和一個(gè)數(shù)組黎做,然后將函數(shù)作用于數(shù)組的每個(gè)元素。雖然 forEach 是這些函數(shù)中最簡(jiǎn)單的松忍,但在函數(shù)式編程中它可能是最少用到的一個(gè)蒸殿。forEach 沒(méi)有返回值,所以只能用在有副作用的函數(shù)調(diào)用中。
.map
其實(shí)最常用的函數(shù)是 map伟桅。類(lèi)似于 forEach敞掘,map 也是將函數(shù)作用于數(shù)組的每個(gè)元素。但與 forEach 不同的是楣铁,map 將函數(shù)的每個(gè)返回值組成一個(gè)新數(shù)組玖雁,并將其返回。示例如下:
R.map(x?=>?x?*?2,?[1,?2,?3])?
//=>?[2,?4,?6]
//這里使用了匿名函數(shù)盖腕,但我們也可以在這里使用具名函數(shù):
const?double?=?x?=>?x?*?2
R.map(double,?[1,?2,?3])
R.map(double,?{x:?1,?y:?2,?z:?3});?//=>?{x:?2,?y:?4,?z:?6}
.filter/ reject
.reject接下來(lái)赫冬,我們來(lái)看看 filter 和 reject。就像名字所示溃列,filter 會(huì)根據(jù)函數(shù)的返回值從數(shù)組中選擇元素劲厌,例如:
const?isEven?=?n?=>?n?%?2?===?0;
R.filter(isEven,?[1,?2,?3,?4]);?//=>?[2,?4]
R.filter(isEven,?{a:?1,?b:?2,?c:?3,?d:?4});?//=>?{b:?2,?d:?4}
.filter將函數(shù)(本例中為 isEven)作用于數(shù)組中的每個(gè)元素。每當(dāng)函數(shù)返回 "true" 時(shí)听隐,相應(yīng)的元素將包含到結(jié)果中补鼻;反之當(dāng)斷言函數(shù)返回為 "falsy" 值時(shí),相應(yīng)的元素將從結(jié)果數(shù)組中排除(過(guò)濾掉)雅任。
reject 是 filter 的補(bǔ)操作风范。 它保留使斷言函數(shù)返回 "falsy" 的元素,排除使斷言函數(shù)返回 "truthy" 的元素沪么。
const?isOdd?=?(n)?=>?n?%?2?===?1;
R.reject(isOdd,?[1,?2,?3,?4]);?//=>?[2,?4]
R.reject(isOdd,?{a:?1,?b:?2,?c:?3,?d:?4});?//=>?{b:?2,?d:?4}
.find
find 將函數(shù)作用于數(shù)組中的每個(gè)元素硼婿,并返回第一個(gè)使函數(shù)返回真值的元素。
R.find(isEven,?[1,?2,?3,?4])?//=>?2
const?xs?=?[{a:?1},?{a:?2},?{a:?3}];
R.find(R.propEq('a',?2))(xs);?//=>?{a:?2}
R.find(R.propEq('a',?4))(xs);?//=>?undefined
.reduce
reduce 比之前遇到的其他函數(shù)要復(fù)雜一些禽车。
reduce 接受一個(gè)二元函數(shù)(reducing function)寇漫、一個(gè)初始值和待處理的數(shù)組。
歸約函數(shù)的第一個(gè)參數(shù)稱(chēng)為 "accumulator" (累加值)殉摔,第二個(gè)參數(shù)取自數(shù)組中的元素州胳;返回值為一個(gè)新的 "accumulator"。
先來(lái)看一個(gè)示例钦勘,然后看看會(huì)發(fā)生什么陋葡。
const?subtract=?(accum,?value)?=>?accum?-?value
R.reduce(subtract,?0,?[1,?2,?3,?4])?//?=>?((((0?-?1)?-?2)?-?3)?-?4)?=?-10
//?-?-10
//?/?\?/?\
//?-?4?-6?4
//?/?\?/?\
//?-?3?==>?-3?3
//?/?\?/?\
//?-?2?-1?2
//?/?\?/?\
//?0?1?0?1
- reduce 首先將初始值 5 和 數(shù)組中的首個(gè)元素 1 傳入歸約函數(shù) subtract,subtract 返回一個(gè)新的累加值:0- 1 = -1彻采。
- reduce 再次調(diào)用subtract腐缤,這次使用新的累加值 -1 和 數(shù)組中的下一個(gè)元素 2 作為參數(shù)subtract返回 -3。
- reduce 再次使用 -3和 數(shù)組中的下個(gè)元素 3 來(lái)調(diào)用 subtract肛响,輸出 -6岭粤。
- reduce 最后一次調(diào)用subtract,使用 -6 和 數(shù)組中的最后一個(gè)元素 4 特笋,輸出 -10剃浇。
- reduce 將最終累加值 -10作為結(jié)果返回以上關(guān)于集合的處理巾兆,是大多數(shù)庫(kù)都或多或少涵蓋了。這里主要是告知大家 Ramda 使用方法在參數(shù)排列的差異虎囚。
Ramda更重要的是接下來(lái)的這些內(nèi)容角塑。
函數(shù)的組合
Ramda 為簡(jiǎn)單的函數(shù)組合提供了一些函數(shù)。這使得我們能操作一些較為復(fù)雜的邏輯淘讥。
函數(shù)的合成
compose:將多個(gè)函數(shù)合并成一個(gè)函數(shù)圃伶,從右到左執(zhí)行。
R.compose(Math.abs,?R.add(1),?R.multiply(2))(-4)?//?7
pipe:將多個(gè)函數(shù)合并成一個(gè)函數(shù)蒲列,從左到右執(zhí)行窒朋。
var?negative?=?x?=>?-1?*?x;
var?increaseOne?=?x?=>?x?+?1;
var?f?=?R.pipe(Math.pow,?negative,?increaseOne);
f(3,?4)?//?-80?=>?-(3^4)?+?1
converge:接受兩個(gè)參數(shù),第一個(gè)參數(shù)是函數(shù)蝗岖,第二個(gè)參數(shù)是函數(shù)數(shù)組侥猩。傳入的值先使用第二個(gè)參數(shù)包含的函數(shù)分別處理以后,再用第一個(gè)參數(shù)處理前一步生成的結(jié)果抵赢。
var?sumOfArr?=?arr?=>?{
??var?sum?=?0;
??arr.forEach(i?=>?sum?+=?i);
??return?sum;
};
var?lengthOfArr?=?arr?=>?arr.length;
var?average?=?R.converge(R.divide,?[sumOfArr,?lengthOfArr])
average([1,?2,?3,?4,?5,?6,?7])
//?4
//?相當(dāng)于?28?除以?7
var?toUpperCase?=?s?=>?s.toUpperCase();
var?toLowerCase?=?s?=>?s.toLowerCase();
var?strangeConcat?=?R.converge(R.concat,?[toUpperCase,?toLowerCase])
strangeConcat("Yodel")
//?"YODELyodel"
//?相當(dāng)于?R.concat('YODEL',?'yodel')
柯里化
curry:將多參數(shù)的函數(shù)欺劳,轉(zhuǎn)換成單參數(shù)的形式。
var?addFourNumbers?=?(a,?b,?c,?d)?=>?a?+?b?+?c?+?d;
var?curriedAddFourNumbers?=?R.curry(addFourNumbers);
var?f?=?curriedAddFourNumbers(1,?2);
var?g?=?f(3);
g(4)?//?10
partial:允許多參數(shù)的函數(shù)接受一個(gè)數(shù)組瓣俯,指定最左邊的部分參數(shù)它掂。
var?multiply2?=?(a,?b)?=>?a?*?b;
var?double?=?R.partial(multiply2,?[2]);
double(2)?//?4
var?greet?=?(salutation,?title,?firstName,?lastName)?=>
??salutation?+?',?'?+?title?+?'?'?+?firstName?+?'?'?+?lastName?+?'!';
var?sayHello?=?R.partial(greet,?['Hello']);
var?sayHelloToMs?=?R.partial(sayHello,?['Ms.']);
sayHelloToMs('Jane',?'Jones');?//=>?'Hello,?Ms.?Jane?Jones!'
complement:返回一個(gè)新函數(shù)呆瞻,如果原函數(shù)返回true,該函數(shù)返回false杏愤;如果原函數(shù)返回false媒区,該函數(shù)返回true驼仪。
var?gt10?=?x?=>?x?>?10;
var?lte10?=?R.complement(gt10);
gt10(7)?//?false
lte10(7)?//?true
函數(shù)的執(zhí)行
binary:參數(shù)函數(shù)執(zhí)行時(shí),只傳入最前面兩個(gè)參數(shù)袜漩。
var?takesThreeArgs?=?function(a,?b,?c)?{
??return?[a,?b,?c];
};
var?takesTwoArgs?=?R.binary(takesThreeArgs);
takesTwoArgs(1,?2,?3)?//?[1,?2,?undefined]
tap:將一個(gè)值傳入指定函數(shù)绪爸,并返回該值。
var?sayX?=?x?=>?console.log('x?is?'?+?x);
R.tap(sayX)(100)?//?100
R.pipe(
??R.assoc('a',?2),
??R.tap(console.log),
??R.assoc('a',?3)
)({a:?1})
//?{a:?3}
zipWith:將兩個(gè)數(shù)組對(duì)應(yīng)位置的值宙攻,一起作為參數(shù)傳入某個(gè)函數(shù)奠货。
var?f?=?(x,?y)?=>?{
??//?...
};
R.zipWith(f,?[1,?2,?3])(['a',?'b',?'c'])
//?[f(1,?'a'),?f(2,?'b'),?f(3,?'c')]
apply:將數(shù)組轉(zhuǎn)成參數(shù)序列,傳入指定函數(shù)座掘。
var?nums?=?[1,?2,?3,?-99,?42,?6,?7];
R.apply(Math.max)(nums)?//?42
還有諸如ascend升序排列递惋、descend降序排列的方法等。
其他
Ramda 還提供了比較運(yùn)算溢陪、數(shù)學(xué)運(yùn)算萍虽、邏輯運(yùn)算、字符串形真、數(shù)組杉编、對(duì)象等的實(shí)用方法。
比如eqBy:比較兩個(gè)值傳入指定函數(shù)的運(yùn)算結(jié)果是否相等。
R.eqBy(Math.abs,?5)(-5)
//?true
比如divide:返回第一個(gè)參數(shù)除以第二個(gè)參數(shù)的商邓馒。
R.divide(71)(100)?//?0.71
比如test:判斷一個(gè)字符串是否匹配給定的正則表達(dá)式嘶朱。
R.test(/^x/)('xyz')
//?true
R.test(/^y/)('xyz')
//?false
比如omit:過(guò)濾對(duì)象指定屬性。
R.omit(['a',?'d'])({a:?1,?b:?2,?c:?3,?d:?4})
//?{b:?2,?c:?3}
Ramda 提供的方法遠(yuǎn)不止這些光酣。歡迎大家參閱官方鏈接见咒。
官方地址:https://ramda.cn/
寫(xiě)在最后
如果我的內(nèi)容能對(duì)你有所幫助缤言,一個(gè)贊同和喜歡鼓勵(lì)一下下~