每個(gè) JavaScript 程序員都應(yīng)該掌握這個(gè)工具!

哈嘍,我是老魚(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ě)在最后

我是老魚(yú),一名致力于在技術(shù)道路上的終身學(xué)習(xí)者挂疆、實(shí)踐者改览、分享者!

如果我的內(nèi)容能對(duì)你有所幫助缤言,一個(gè)贊同和喜歡鼓勵(lì)一下下~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宝当,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子胆萧,更是在濱河造成了極大的恐慌庆揩,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跌穗,死亡現(xiàn)場(chǎng)離奇詭異订晌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蚌吸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)锈拨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人羹唠,你說(shuō)我怎么就攤上這事奕枢。” “怎么了佩微?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵缝彬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我哺眯,道長(zhǎng)谷浅,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任奶卓,我火速辦了婚禮一疯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寝杖。我一直安慰自己违施,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布瑟幕。 她就那樣靜靜地躺著磕蒲,像睡著了一般留潦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辣往,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天兔院,我揣著相機(jī)與錄音,去河邊找鬼站削。 笑死坊萝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的许起。 我是一名探鬼主播十偶,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼园细!你這毒婦竟也來(lái)了惦积?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤猛频,失蹤者是張志新(化名)和其女友劉穎狮崩,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鹿寻,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睦柴,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了毡熏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坦敌。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖招刹,靈堂內(nèi)的尸體忽然破棺而出恬试,到底是詐尸還是另有隱情,我是刑警寧澤疯暑,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站哑舒,受9級(jí)特大地震影響妇拯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洗鸵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一越锈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膘滨,春花似錦甘凭、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)德撬。三九已至,卻和暖如春躲胳,著一層夾襖步出監(jiān)牢的瞬間蜓洪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工坯苹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隆檀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓粹湃,卻偏偏與公主長(zhǎng)得像恐仑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子为鳄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容