每個 JavaScript 程序員都應該掌握這個工具!

哈嘍,我是老魚!
最近對一個工具庫的使用上癮了!這個給大家分享下。這是每個 JavaScript 程序員都應該掌握的工具:Ramda

簡介

Ramda 是一款實用的 JavaScript 函數式編程庫。類似的庫中,大家最為熟悉的有Underscore、 Lodash等。

這時大家可能會問:

既然 Underscore 和 Lodash 已經這么流行了,為什么還要學習好像雷同的 Ramda 呢?

回答是,Ramda 強調更加純粹的函數式風格。數據不變性和函數無副作用是其核心設計理念。這可以幫助你使用簡潔、優(yōu)雅的代碼來完成工作。

Ramda 的目標更為專注:專門為函數式編程風格而設計,更容易創(chuàng)建函數式 pipeline、且從不改變用戶已有數據。

對比區(qū)分

Underscore 和 Lodash的參數位置不對,把處理的數據放到了第一個參數。

var?square?=?n?=>?n?*?n;
_.map([4,?8],?square)?//?[16,?64]

上面代碼中,_.map的第一個參數[4, 8]是要處理的數據,第二個參數square是數據要執(zhí)行的運算。

Ramda 的數據一律放在最后一個參數,理念是"function first,data last"。

Ramda應該是目前最符合函數式編程的工具庫,它需要操作的數據參數都是放在最后的。

var?R?=?require('ramda');
R.map(square,?[4,?8])?//?[16,?64]

把要操作的數據放在最后一個參數,作為參數排列順序,不僅滿足 Ramda 的核心設計,同時使得所有方法都支持柯里化。

安裝

使用 node:

$?npm?install?ramda

或從 CDN 上獲?。?/p>

<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');

也就是說,所有多參數的函數,默認都可以單參數使用。也可以讓你在只提供部分參數的情況下,輕松地在已有函數的基礎上創(chuàng)建新函數。

//?寫法一
R.map(square,?[4,?8])

//?寫法二
R.map(square)([4,?8])
//?或者
var?mapSquare?=?R.map(square);
mapSquare([4,?8]);

上面代碼中,寫法一是多參數版本,寫法二是柯里化以后的單參數版本。Ramda 都支持,并且推薦使用第二種寫法。

今天,接下來是我總結的Ramda的幾種常見的使用場景,展示怎樣用 Ramda 寫出既簡潔易讀,又方便擴展復用的代碼。

使用集合迭代函數代替循環(huán)

.forEach

不必寫顯式for循環(huán),而是用 forEach 函數代替循環(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 接受一個函數和一個數組,然后將函數作用于數組的每個元素。雖然 forEach 是這些函數中最簡單的,但在函數式編程中它可能是最少用到的一個。forEach 沒有返回值,所以只能用在有副作用的函數調用中。

.map

其實最常用的函數是 map。類似于 forEach,map 也是將函數作用于數組的每個元素。但與 forEach 不同的是,map 將函數的每個返回值組成一個新數組,并將其返回。示例如下:

R.map(x?=>?x?*?2,?[1,?2,?3])?
//=>?[2,?4,?6]
//這里使用了匿名函數,但我們也可以在這里使用具名函數:
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接下來,我們來看看 filter 和 reject。就像名字所示,filter 會根據函數的返回值從數組中選擇元素,例如:

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將函數(本例中為 isEven)作用于數組中的每個元素。每當函數返回 "true" 時,相應的元素將包含到結果中;反之當斷言函數返回為 "falsy" 值時,相應的元素將從結果數組中排除(過濾掉)。

reject 是 filter 的補操作。 它保留使斷言函數返回 "falsy" 的元素,排除使斷言函數返回 "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 將函數作用于數組中的每個元素,并返回第一個使函數返回真值的元素。

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 比之前遇到的其他函數要復雜一些。

reduce 接受一個二元函數(reducing function)、一個初始值和待處理的數組。

歸約函數的第一個參數稱為 "accumulator" (累加值),第二個參數取自數組中的元素;返回值為一個新的 "accumulator"。

先來看一個示例,然后看看會發(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 和 數組中的首個元素 1 傳入歸約函數 subtract,subtract 返回一個新的累加值:0- 1 = -1。
  • reduce 再次調用subtract,這次使用新的累加值 -1 和 數組中的下一個元素 2 作為參數subtract返回 -3。
  • reduce 再次使用 -3和 數組中的下個元素 3 來調用 subtract,輸出 -6。
  • reduce 最后一次調用subtract,使用 -6 和 數組中的最后一個元素 4 ,輸出 -10。
  • reduce 將最終累加值 -10作為結果返回以上關于集合的處理,是大多數庫都或多或少涵蓋了。這里主要是告知大家 Ramda 使用方法在參數排列的差異。

Ramda更重要的是接下來的這些內容。

函數的組合

Ramda 為簡單的函數組合提供了一些函數。這使得我們能操作一些較為復雜的邏輯。

函數的合成

compose:將多個函數合并成一個函數,從右到左執(zhí)行。

R.compose(Math.abs,?R.add(1),?R.multiply(2))(-4)?//?7

pipe:將多個函數合并成一個函數,從左到右執(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:接受兩個參數,第一個參數是函數,第二個參數是函數數組。傳入的值先使用第二個參數包含的函數分別處理以后,再用第一個參數處理前一步生成的結果。

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
//?相當于?28?除以?7

var?toUpperCase?=?s?=>?s.toUpperCase();
var?toLowerCase?=?s?=>?s.toLowerCase();
var?strangeConcat?=?R.converge(R.concat,?[toUpperCase,?toLowerCase])
strangeConcat("Yodel")
//?"YODELyodel"
//?相當于?R.concat('YODEL',?'yodel')

柯里化

curry:將多參數的函數,轉換成單參數的形式。

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:允許多參數的函數接受一個數組,指定最左邊的部分參數。

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:返回一個新函數,如果原函數返回true,該函數返回false;如果原函數返回false,該函數返回true。

var?gt10?=?x?=>?x?>?10;
var?lte10?=?R.complement(gt10);
gt10(7)?//?false
lte10(7)?//?true

函數的執(zhí)行

binary:參數函數執(zhí)行時,只傳入最前面兩個參數。

var?takesThreeArgs?=?function(a,?b,?c)?{
??return?[a,?b,?c];
};

var?takesTwoArgs?=?R.binary(takesThreeArgs);
takesTwoArgs(1,?2,?3)?//?[1,?2,?undefined]

tap:將一個值傳入指定函數,并返回該值。

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:將兩個數組對應位置的值,一起作為參數傳入某個函數。

var?f?=?(x,?y)?=>?{
??//?...
};
R.zipWith(f,?[1,?2,?3])(['a',?'b',?'c'])
//?[f(1,?'a'),?f(2,?'b'),?f(3,?'c')]

apply:將數組轉成參數序列,傳入指定函數。

var?nums?=?[1,?2,?3,?-99,?42,?6,?7];
R.apply(Math.max)(nums)?//?42

還有諸如ascend升序排列、descend降序排列的方法等。

其他

Ramda 還提供了比較運算、數學運算、邏輯運算、字符串、數組、對象等的實用方法。

比如eqBy:比較兩個值傳入指定函數的運算結果是否相等。

R.eqBy(Math.abs,?5)(-5)
//?true

比如divide:返回第一個參數除以第二個參數的商。

R.divide(71)(100)?//?0.71

比如test:判斷一個字符串是否匹配給定的正則表達式。

R.test(/^x/)('xyz')
//?true

R.test(/^y/)('xyz')
//?false

比如omit:過濾對象指定屬性。

R.omit(['a',?'d'])({a:?1,?b:?2,?c:?3,?d:?4})
//?{b:?2,?c:?3}

Ramda 提供的方法遠不止這些。歡迎大家參閱官方鏈接。

官方地址:https://ramda.cn/

寫在最后

我是老魚,一名致力于在技術道路上的終身學習者、實踐者、分享者!

如果我的內容能對你有所幫助,一個贊同和喜歡鼓勵一下下~

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容