我发现,这是一个很重要的库,提供了许多有用的方法,每个 JavaScript 程序员都应该掌握这个工具。
你可能会问,Underscore 和 Lodash 已经这么流行了,为什么还要学习好像雷同的 Ramda 呢?
回答是,前两者的参数位置不对,把处理的数据放到了第一个参数。
var square = n => n * n; _.map([4, 8], square) // [16, 64]
上面代码中,_.map
的第一个参数[4, 8]
是要处理的数据,第二个参数square
是数据要执行的运算。
Ramda 的数据一律放在最后一个参数,理念是"function first,data last"。
var R = require('ramda'); R.map(square, [4, 8]) // [16, 64]
为什么 Underscore 和 Lodash 是错的,而 Ramda 是对的?这放在下一篇文章详细解释,今天我主要介绍 Ramda 提供的几十个方法。这是理解以后的内容所必须的。
除了数据放在最后一个参数,Ramda 还有一个特点:所有方法都支持柯里化。
也就是说,所有多参数的函数,默认都可以单参数使用。
// 写法一 R.map(square, [4, 8]) // 写法二 R.map(square)([4, 8]) // 或者 var mapSquare = R.map(square); mapSquare([4, 8]);
上面代码中,写法一是多参数版本,写法二是柯里化以后的单参数版本。Ramda 都支持,并且推荐使用第二种写法。
由于这两个特点,使得 Ramda 成为 JavaScript 函数式编程最理想的工具库。今天,我先介绍它的 API,下一次再介绍这些方法如何用于实战。我保证,一旦你理解了它的运算模型,就一定会认同这才是正确的计算方法。
下面所有的示例,都可以在线上测试环境运行。
Ramda API 介绍
目录
一、比较运算
二、数学运算
三、逻辑运算
四、字符串
五、函数
- 5.1 函数的合成
- 5.2 柯里化
- 5.3 函数的执行
六、数组
- 6.1 数组的特征判断
- 6.2 数组的截取和添加
- 6.3 数组的过滤
- 6.4 单数组运算
- 6.5 双数组运算
- 6.6 复合数组
七、对象
- 7.1 对象的特征判断
- 7.2 对象的过滤
- 7.3 对象的截取
- 7.4 对象的运算
- 7.5 复合对象
一、比较运算
gt
:判断第一个参数是否大于第二个参数。
R.gt(2)(1) // true R.gt('a')('z') // false
gte
:判断第一个参数是否大于等于第二个参数。
R.gte(2)(2) // true R.gte('a')('z') // false
lt
:判断第一个参数是否小于第二个参数。
R.lt(2)(1) // false R.lt('a')('z') // true
lte
:判断第一个参数是否小于等于第二个参数。
R.lte(2)(2) // true R.lte('a')('z') // true
equals
:比较两个值是否相等(支持对象的比较)。
R.equals(1)(1) // true R.equals(1)('1') // false R.equals([1, 2, 3])([1, 2, 3]) // true var a = {}; a.v = a; var b = {}; b.v = b; R.equals(a)(b) // true
eqBy
:比较两个值传入指定函数的运算结果是否相等。
R.eqBy(Math.abs, 5)(-5) // true
二、数学运算
add
:返回两个值的和。
R.add(7)(10) // 17
subtract
:返回第一个参数减第二个参数的差。
R.subtract(10)(8) // 2
multiply
:返回两个值的积。
R.multiply(2)(5) // 10
divide
:返回第一个参数除以第二个参数的商。
R.divide(71)(100) // 0.71
三、逻辑运算
either
:接受两个函数作为参数,只要有一个返回true
,就返回true
,否则返回false
。相当于||
运算。
var gt10 = x => x > 10; var even = x => x % 2 === 0; var f = R.either(gt10, even); f(101) // true f(8) // true
both
:接受两个函数作为参数,只有它们都返回true
,才返回true
,否则返回false
,相当于&&
运算。
var gt10 = x => x > 10; var even = x => x % 2 === 0; var f = R.both(gt10, even); f(15) // false f(30) // true
allPass
:接受一个函数数组作为参数,只有它们都返回true
,才返回true
,否则返回false
。
var gt10 = x => x > 10; var even = x => x % 2 === 0; var isEvenAndGt10 = R.allPass([gt10, even]); isEvenAndGt10(15) // false isEvenAndGt10(30) // true
四、字符串
split
:按照指定分隔符将字符串拆成一个数组。
R.split('.')('a.b.c.xyz.d') // ['a', 'b', 'c', 'xyz', 'd']
test
:判断一个字符串是否匹配给定的正则表达式。
R.test(/^x/)('xyz') // true R.test(/^y/)('xyz') // false
match
:返回一个字符串的匹配结果。
R.match(/([a-z]a)/g)('bananas') // ['ba', 'na', 'na'] R.match(/a/)('b') // [] R.match(/a/)(null) // TypeError: null does not have a method named "match"
五、函数
5.1 函数的合成
compose
:将多个函数合并成一个函数,从右到左执行。
R.compose(Math.abs, R.add(1), R.multiply(2))(-4) // 7
pipe
:将多个函数合并成一个函数,从左到右执行。
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')
5.2 柯里化
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!'
partialRight
:与partial
类似,但数组指定的参数为最右边的参数。
var greet = (salutation, title, firstName, lastName) => salutation + ', ' + title + ' ' + firstName + ' ' + lastName + '!'; var greetMsJaneJones = R.partialRight(greet, ['Ms.', 'Jane', 'Jones']); greetMsJaneJones('Hello') // 'Hello, Ms. Jane Jones!'
useWith
:接受一个函数fn
和一个函数数组fnList
作为参数,返回fn
的柯里化版本。该新函数的参数,先分别经过对应的fnList
成员处理,再传入fn
执行。
var decreaseOne = x => x - 1; var increaseOne = x => x + 1; R.useWith(Math.pow, [decreaseOne, increaseOne])(3, 4) // 32 R.useWith(Math.pow, [decreaseOne, increaseOne])(3)(4) // 32
memoize
:返回一个函数,会缓存每一次的运行结果。
var productOfArr = arr => { var product = 1; arr.forEach(i => product *= i); return product; }; var count = 0; var factorial = R.memoize(n => { count += 1; return productOfArr(R.range(1, n + 1)); }); factorial(5) // 120 factorial(5) // 120 factorial(5) // 120 count // 1
complement
:返回一个新函数,如果原函数返回true
,该函数返回false
;如果原函数返回false
,该函数返回true
。
var gt10 = x => x > 10; var lte10 = R.complement(gt10); gt10(7) // false lte10(7) // true
5.3 函数的执行
binary
:参数函数执行时,只传入最前面两个参数。
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
applySpec
:返回一个模板函数,该函数会将参数传入模板内的函数执行,然后将执行结果填充到模板。
var getMetrics = R.applySpec({ sum: R.add, nested: { mul: R.multiply } }); getMetrics(2, 4) // { sum: 6, nested: { mul: 8 } }
ascend
:返回一个升序排列的比较函数,主要用于排序。
var byAge = R.ascend(R.prop('age')); var people = [ // ... ]; var peopleByYoungestFirst = R.sort(byAge)(people);
descend
:返回一个降序排列的比较函数,主要用于排序。
var byAge = R.descend(R.prop('age')); var people = [ // ... ]; var peopleByOldestFirst = R.sort(byAge)(people);
六、数组
6.1 数组的特征判断
contains
:如果包含某个成员,返回true
。
R.contains(3)([1, 2, 3]) // true R.contains(4)([1, 2, 3]) // false R.contains({ name: 'Fred' })([{ name: 'Fred' }]) // true R.contains([42])([[42]]) // true
all
:所有成员都满足指定函数时,返回true
,否则返回false
var equals3 = R.equals(3); R.all(equals3)([3, 3, 3, 3]) // true R.all(equals3)([3, 3, 1, 3]) // false
any
:只要有一个成员满足条件,就返回true
。
var lessThan0 = R.flip(R.lt)(0); var lessThan2 = R.flip(R.lt)(2); R.any(lessThan0)([1, 2]) // false R.any(lessThan2)([1, 2]) // true
none
:没有成员满足条件时,返回true
。
var isEven = n => n % 2 === 0; R.none(isEven)([1, 3, 5, 7, 9, 11]) // true R.none(isEven)([1, 3, 5, 7, 8, 11]) // false
6.2 数组的截取和添加
head
:返回数组的第一个成员。
R.head(['fi', 'fo', 'fum']) // 'fi' R.head([]) // undefined R.head('abc') // 'a' R.head('') // ''
last
:返回数组的最后一个成员。
R.last(['fi', 'fo', 'fum']) // 'fum' R.last([]) // undefined R.last('abc') // 'c' R.last('') // ''
tail
:返回第一个成员以外的所有成员组成的新数组。
R.tail([1, 2, 3]) // [2, 3] R.tail([1, 2]) // [2] R.tail([1]) // [] R.tail([]) // [] R.tail('abc') // 'bc' R.tail('ab') // 'b' R.tail('a') // '' R.tail('') // ''
init
:返回最后一个成员以外的所有成员组成的新数组。
R.init([1, 2, 3]) // [1, 2] R.init([1, 2]) // [1] R.init([1]) // [] R.init([]) // [] R.init('abc') // 'ab' R.init('ab') // 'a' R.init('a') // '' R.init('') // ''
nth
:取出指定位置的成员。
var list = ['foo', 'bar', 'baz', 'quux']; R.nth(1)(list) // 'bar' R.nth(-1)(list) // 'quux' R.nth(-99)(list) // undefined R.nth(2)('abc') // 'c' R.nth(3)('abc') // ''
take
:取出前 n 个成员。
R.take(1)(['foo', 'bar', 'baz']) // ['foo'] R.take(2)(['foo', 'bar', 'baz']) // ['foo', 'bar'] R.take(3)(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz'] R.take(4)(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz'] R.take(3)('ramda') // 'ram'
takeLast
:取出后 n 个成员。
R.takeLast(1)(['foo', 'bar', 'baz']) // ['baz'] R.takeLast(2)(['foo', 'bar', 'baz']) // ['bar', 'baz'] R.takeLast(3)(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz'] R.takeLast(4)(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz'] R.takeLast(3)('ramda') // 'mda'
slice
:从起始位置(包括)开始,到结束位置(不包括)为止,从原数组截取出一个新数组。
R.slice(1, 3)(['a', 'b', 'c', 'd']) // ['b', 'c'] R.slice(1, Infinity)(['a', 'b', 'c', 'd']) // ['b', 'c', 'd'] R.slice(0, -1)(['a', 'b', 'c', 'd']) // ['a', 'b', 'c'] R.slice(-3, -1)(['a', 'b', 'c', 'd']) // ['b', 'c'] R.slice(0, 3)('ramda') // 'ram'
remove
:移除开始位置后的n
个成员。
R.remove(2, 3)([1,2,3,4,5,6,7,8]) // [1,2,6,7,8]
insert
:在指定位置插入给定值。
R.insert(2, 'x')([1,2,3,4]) // [1,2,'x',3,4]
insertAll
:在指定位置,插入另一个数组的所有成员。
R.insertAll(2,['x','y','z'])([1,2,3,4]) // [1,2,'x','y','z',3,4]
prepend
:在数组头部插入一个成员
R.prepend('fee')(['fi', 'fo', 'fum']) // ['fee', 'fi', 'fo', 'fum']
append
:在数组尾部追加新的成员。
R.append('tests')(['write', 'more']) // ['write', 'more', 'tests'] R.append('tests')([]) // ['tests'] R.append(['tests'])(['write', 'more']) // ['write', 'more', ['tests']]
intersperse
:在数组成员之间插入表示分隔的成员。
R.intersperse('n')(['ba', 'a', 'a']) // ['ba', 'n', 'a', 'n', 'a']
join
:将数组合并成一个字符串,并在成员之间插入分隔符。
R.join('|')([1, 2, 3]) // '1|2|3'
6.3 数组的过滤
filter
:过滤出符合条件的成员。
var isEven = n => n % 2 === 0; R.filter(isEven)([1, 2, 3, 4]) // [2, 4]
reject
:过滤出所有不满足条件的成员。
var isOdd = (n) => n % 2 === 1; R.reject(isOdd)([1, 2, 3, 4]) // [2, 4]
takeWhile
: 一旦满足条件,后面的成员都会被过滤。
var isNotFour = x => x !== 4; R.takeWhile(isNotFour)([1, 2, 3, 4, 3, 2, 1]) // [1, 2, 3]
dropWhile
:一旦不满足条件,取出剩余的所有成员。
var lteTwo = x => x <= 2; R.dropWhile(lteTwo)([1, 2, 3, 4, 3, 2, 1]) // [3, 4, 3, 2, 1]
without
:返回指定值以外的成员。
R.without([1, 2])([1, 2, 1, 3, 4]) // [3, 4]
6.4 单数组运算
countBy
:对每个成员执行指定函数以后,返回一个对象,表示各种执行结果分别包含多少成员。
var numbers = [1.0, 1.1, 1.2, 2.0, 3.0, 2.2]; R.countBy(Math.floor)(numbers) // {'1': 3, '2': 2, '3': 1} var letters = ['a', 'b', 'A', 'a', 'B', 'c']; R.countBy(R.toLower)(letters) // {'a': 3, 'b': 2, 'c': 1}
splitAt
:在给定位置,将原数组分成两个部分。
R.splitAt(1)([1, 2, 3]) // [[1], [2, 3]] R.splitAt(5)('hello world') // ['hello', ' world'] R.splitAt(-1)('foobar') // ['fooba', 'r']
splitEvery
:按照指定的个数,将原数组分成多个部分。
R.splitEvery(3)([1, 2, 3, 4, 5, 6, 7]) // [[1, 2, 3], [4, 5, 6], [7]] R.splitEvery(3)('foobarbaz') // ['foo', 'bar', 'baz']
splitWhen
:以第一个满足指定函数的成员为界,将数组分成两个部分。
R.splitWhen(R.equals(2))([1, 2, 3, 1, 2, 3]) // [[1], [2, 3, 1, 2, 3]]
aperture
:每个成员与其后给定数量的成员分成一组,这些组构成一个新的数组。
R.aperture(3)([1, 2, 3, 4, 5, 6, 7]) // [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7]]
partition
:根据是否满足指定函数,将成员分区。
R.partition(R.contains('s'))(['sss', 'ttt', 'foo', 'bars']) // => [ [ 'sss', 'bars' ], [ 'ttt', 'foo' ] ]
indexOf
:某个值在数组中第一次出现的位置。
R.indexOf(3)([1,2,3,4]) // 2 R.indexOf(10)([1,2,3,4]) // -1
lastIndexOf
:某个值在数组中最后一次出现的位置。
R.lastIndexOf(3)([-1,3,3,0,1,2,3,4]) // 6 R.lastIndexOf(10)([1,2,3,4]) // -1
map
:数组的每个成员依次执行某个函数。
var double = x => x * 2; R.map(double)([1, 2, 3]) // [2, 4, 6]
mapIndexed
:与map
类似,区别是遍历函数可以额外获得两个参数:索引位置和原数组。
var mapIndexed = R.addIndex(R.map); mapIndexed( (val, idx) => idx + '-' + val, ['f', 'o', 'o', 'b', 'a', 'r'] ) // ['0-f', '1-o', '2-o', '3-b', '4-a', '5-r']
forEach
:数组的每个成员依次执行某个函数,总是返回原数组。
var printXPlusFive = x => console.log(x + 5); R.forEach(printXPlusFive, [1, 2, 3]) // [1, 2, 3] // logs 6 // logs 7 // logs 8
reduce
:数组成员依次执行指定函数,每一次的运算结果都会进入一个累积变量。
var mySubtract = function (a, b) { return a - b; }; R.reduce(mySubtract, 0)([1, 2, 3, 4]) // -10
reduceRight
:与reduce
类似,区别是数组成员从左到右执行。
R.reduceRight(R.subtract, 0)([1, 2, 3, 4]) // -2
reduceWhile
:与reduce
类似,区别是有一个判断函数,一旦数组成员不符合条件,就停止累积。
var isOdd = (acc, x) => x % 2 === 1; var xs = [1, 3, 5, 60, 777, 800]; R.reduceWhile(isOdd, R.add, 0)(xs) // 9 var ys = [2, 4, 6]; R.reduceWhile(isOdd, R.add, 111)(ys) // 111
sort
:按照给定函数,对数组进行排序。
var diff = function(a, b) { return a - b; }; R.sort(diff)([4,2,7,5]) // [2, 4, 5, 7]
sortWith
:按照给定的一组函数,进行多重排序。
var alice = { name: 'alice', age: 40 }; var bob = { name: 'bob', age: 30 }; var clara = { name: 'clara', age: 40 }; var people = [clara, bob, alice]; var ageNameSort = R.sortWith([ R.descend(R.prop('age')), R.ascend(R.prop('name')) ]); ageNameSort(people); //=> [alice, clara, bob]
adjust
:对指定位置的成员执行给定的函数。
R.adjust(R.add(10), 1)([1, 2, 3]) // [1, 12, 3] R.adjust(R.add(10),1)([1, 2, 3]) // [1, 12, 3]
ap
:数组成员分别执行一组函数,将结果合成为一个新数组。
R.ap([R.multiply(2), R.add(3)])([1,2,3]) // [2, 4, 6, 4, 5, 6] R.ap([R.concat('tasty '), R.toUpper])(['pizza', 'salad']) // ["tasty pizza", "tasty salad", "PIZZA", "SALAD"]
flatten
:将嵌套数组铺平。
R.flatten([1, 2, [3, 4], 5, [6, [7, 8, [9, [10, 11], 12]]]]) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
groupBy
:将数组成员依次按照指定条件两两比较,并按照结果将所有成员放入子数组。
R.groupWith(R.equals)([0, 1, 1, 2, 3, 5, 8, 13, 21]) // [[0], [1, 1], [2], [3], [5], [8], [13], [21]] R.groupWith((a, b) => a % 2 === b % 2)([0, 1, 1, 2, 3, 5, 8, 13, 21]) // [[0], [1, 1], [2], [3, 5], [8], [13, 21]] R.groupWith(R.eqBy(isVowel), 'aestiou') //=> ['ae', 'st', 'iou']
6.5 双数组运算
concat
:将两个数组合并成一个数组。
R.concat('ABC')('DEF') // 'ABCDEF' R.concat([4, 5, 6])([1, 2, 3]) // [4, 5, 6, 1, 2, 3] R.concat([])([]) // []
zip
:将两个数组指定位置的成员放在一起,生成一个新数组。
R.zip([1, 2, 3])(['a', 'b', 'c']) // [[1, 'a'], [2, 'b'], [3, 'c']]
zipObj
:将两个数组指定位置的成员分别作为键名和键值,生成一个新对象。
R.zipObj(['a', 'b', 'c'])([1, 2, 3]) // {a: 1, b: 2, c: 3}
xprod
:将两个数组的成员两两混合,生成一个新数组。
R.xprod([1, 2])(['a', 'b']) // [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]
intersection
:返回两个数组相同的成员组成的新数组。
R.intersection([1,2,3,4], [7,6,5,4,3]) // [4, 3]
intersectionWith
:返回经过某种运算,有相同结果的两个成员。
var buffaloSpringfield = [ {id: 824, name: 'Richie Furay'}, {id: 956, name: 'Dewey Martin'}, {id: 313, name: 'Bruce Palmer'}, {id: 456, name: 'Stephen Stills'}, {id: 177, name: 'Neil Young'} ]; var csny = [ {id: 204, name: 'David Crosby'}, {id: 456, name: 'Stephen Stills'}, {id: 539, name: 'Graham Nash'}, {id: 177, name: 'Neil Young'} ]; R.intersectionWith(R.eqBy(R.prop('id')),buffaloSpringfield)(csny) // [{id: 456, name: 'Stephen Stills'}, {id: 177, name: 'Neil Young'}]
difference
:返回第一个数组不包含在第二个数组里面的成员。
R.difference([1,2,3,4])([7,6,5,4,3]) // [1,2] R.difference([7,6,5,4,3])([1,2,3,4]) // [7,6,5] R.difference([{a: 1}, {b: 2}])([{a: 1}, {c: 3}]) // [{b: 2}]
differenceWith
:返回执行指定函数后,第一个数组里面不符合条件的所有成员。
var cmp = (x, y) => x.a === y.a; var l1 = [{a: 1}, {a: 2}, {a: 3}]; var l2 = [{a: 3}, {a: 4}]; R.differenceWith(cmp, l1)(l2) // [{a: 1}, {a: 2}]
symmetricDifference
:返回两个数组的非共有成员所组成的一个新数组。
R.symmetricDifference([1,2,3,4])([7,6,5,4,3]) // [1,2,7,6,5] R.symmetricDifference([7,6,5,4,3])([1,2,3,4]) // [7,6,5,1,2]
symmetricDifferenceWith
:根据指定条件,返回两个数组所有运算结果不相等的成员所组成的新数组。
var eqA = R.eqBy(R.prop('a')); var l1 = [{a: 1}, {a: 2}, {a: 3}, {a: 4}]; var l2 = [{a: 3}, {a: 4}, {a: 5}, {a: 6}]; R.symmetricDifferenceWith(eqA, l1, l2) // [{a: 1}, {a: 2}, {a: 5}, {a: 6}]
6.6 复合数组
find
:返回符合指定条件的成员。
var xs = [{a: 1}, {a: 2}, {a: 3}]; R.find(R.propEq('a', 2))(xs) // {a: 2} R.find(R.propEq('a', 4))(xs) // undefined
findIndex
:返回符合指定条件的成员的位置。
var xs = [{a: 1}, {a: 2}, {a: 3}]; R.findIndex(R.propEq('a', 2))(xs) // 1 R.findIndex(R.propEq('a', 4))(xs) // -1
findLast
:返回最后一个符合指定条件的成员。
var xs = [{a: 1, b: 0}, {a:1, b: 1}]; R.findLast(R.propEq('a', 1))(xs) // {a: 1, b: 1} R.findLast(R.propEq('a', 4))(xs) // undefined
findLastIndex
:返回最后一个符合指定条件的成员的位置。
var xs = [{a: 1, b: 0}, {a:1, b: 1}]; R.findLastIndex(R.propEq('a', 1))(xs) // 1 R.findLastIndex(R.propEq('a', 4))(xs) // -1
pluck
:取出数组成员的某个属性,组成一个新数组。
R.pluck('a')([{a: 1}, {a: 2}]) // [1, 2] R.pluck(0)([[1, 2], [3, 4]]) // [1, 3]
project
:取出数组成员的多个属性,组成一个新数组。
var abby = {name: 'Abby', age: 7, hair: 'blond', grade: 2}; var fred = {name: 'Fred', age: 12, hair: 'brown', grade: 7}; var kids = [abby, fred]; R.project(['name', 'grade'])(kids) // [{name: 'Abby', grade: 2}, {name: 'Fred', grade: 7}]
transpose
:将每个成员相同位置的值,组成一个新数组。
R.transpose([[1, 'a'], [2, 'b'], [3, 'c']]) // [[1, 2, 3], ['a', 'b', 'c']] R.transpose([[1, 2, 3], ['a', 'b', 'c']]) // [[1, 'a'], [2, 'b'], [3, 'c']] R.transpose([[10, 11], [20], [], [30, 31, 32]]) // [[10, 20, 30], [11, 31], [32]]
mergeAll
:将数组的成员合并成一个对象。
R.mergeAll([{foo:1},{bar:2},{baz:3}]) // {foo:1,bar:2,baz:3} R.mergeAll([{foo:1},{foo:2},{bar:2}]) // {foo:2, bar:2}
fromPairs
:将嵌套数组转为一个对象。
R.fromPairs([['a', 1], ['b', 2], ['c', 3]]) // {a: 1, b: 2, c: 3}
groupBy
:将数组成员按照指定条件分组。
var byGrade = R.groupBy(function(student) { var score = student.score; return score < 65 ? 'F' : score < 70 ? 'D' : score < 80 ? 'C' : score < 90 ? 'B' : 'A'; }); var students = [{name: 'Abby', score: 84}, {name: 'Eddy', score: 58}, // ... {name: 'Jack', score: 69}]; byGrade(students); // { // 'A': [{name: 'Dianne', score: 99}], // 'B': [{name: 'Abby', score: 84}] // // ..., // 'F': [{name: 'Eddy', score: 58}] // }
sortBy
:根据成员的某个属性排序。
var sortByFirstItem = R.sortBy(R.prop(0)); sortByFirstItem([[-1, 1], [-2, 2], [-3, 3]]) // [[-3, 3], [-2, 2], [-1, 1]] var sortByNameCaseInsensitive = R.sortBy( R.compose(R.toLower, R.prop('name')) ); var alice = {name: 'ALICE', age: 101}; var bob = {name: 'Bob', age: -10}; var clara = {name: 'clara', age: 314.159}; var people = [clara, bob, alice]; sortByNameCaseInsensitive(people) // [alice, bob, clara]
七、对象
7.1 对象的特征判断
has
: 返回一个布尔值,表示对象自身是否具有该属性。
var hasName = R.has('name') hasName({name: 'alice'}) //=> true hasName({name: 'bob'}) //=> true hasName({}) //=> false var point = {x: 0, y: 0}; var pointHas = R.has(R.__, point); pointHas('x') // true pointHas('y') // true pointHas('z') // false
hasIn
:返回一个布尔值,表示对象自身或原型链上是否具有某个属性。
function Rectangle(width, height) { this.width = width; this.height = height; } Rectangle.prototype.area = function() { return this.width * this.height; }; var square = new Rectangle(2, 2); R.hasIn('width')(square) // true R.hasIn('area')(square) // true
propEq
:如果属性等于给定值,返回true
。
var abby = {name: 'Abby', age: 7, hair: 'blond'}; var fred = {name: 'Fred', age: 12, hair: 'brown'}; var rusty = {name: 'Rusty', age: 10, hair: 'brown'}; var alois = {name: 'Alois', age: 15, disposition: 'surly'}; var kids = [abby, fred, rusty, alois]; var hasBrownHair = R.propEq('hair', 'brown'); R.filter(hasBrownHair)(kids) // [fred, rusty]
whereEq
:如果属性等于给定值,返回true
。
var pred = R.whereEq({a: 1, b: 2}); pred({a: 1}) // false pred({a: 1, b: 2}) // true pred({a: 1, b: 2, c: 3}) // true pred({a: 1, b: 1}) // false
where
:如果各个属性都符合指定条件,返回true
。
var pred = R.where({ a: R.equals('foo'), b: R.complement(R.equals('bar')), x: R.gt(__, 10), y: R.lt(__, 20) }); pred({a: 'foo', b: 'xxx', x: 11, y: 19}) // true pred({a: 'xxx', b: 'xxx', x: 11, y: 19}) // false pred({a: 'foo', b: 'bar', x: 11, y: 19}) // false pred({a: 'foo', b: 'xxx', x: 10, y: 19}) // false pred({a: 'foo', b: 'xxx', x: 11, y: 20}) // false
7.2 对象的过滤
omit
:过滤指定属性。
R.omit(['a', 'd'])({a: 1, b: 2, c: 3, d: 4}) // {b: 2, c: 3}
filter
:返回所有满足条件的属性
var isEven = n => n % 2 === 0; R.filter(isEven)({a: 1, b: 2, c: 3, d: 4}) // {b: 2, d: 4}
reject
:返回所有不满足条件的属性
var isOdd = (n) => n % 2 === 1; R.reject(isOdd)({a: 1, b: 2, c: 3, d: 4}) // {b: 2, d: 4}
7.3 对象的截取
dissoc
:过滤指定属性。
R.dissoc('b')({a: 1, b: 2, c: 3}) // {a: 1, c: 3}
assoc
:添加或改写某个属性。
R.assoc('c', 3)({a: 1, b: 2}) // {a: 1, b: 2, c: 3}
partition
:根据属性值是否满足给定条件,将属性分区。
R.partition(R.contains('s'))({ a: 'sss', b: 'ttt', foo: 'bars' }) // [ { a: 'sss', foo: 'bars' }, { b: 'ttt' } ]
pick
:返回指定属性组成的新对象
R.pick(['a', 'd'])({a: 1, b: 2, c: 3, d: 4}) // {a: 1, d: 4} R.pick(['a', 'e', 'f'])({a: 1, b: 2, c: 3, d: 4}) // {a: 1}
pickAll
:与pick
类似,但会包括不存在的属性。
R.pickAll(['a', 'd'])({a: 1, b: 2, c: 3, d: 4}) // {a: 1, d: 4} R.pickAll(['a', 'e', 'f'])({a: 1, b: 2, c: 3, d: 4}) // {a: 1, e: undefined, f: undefined}
pickBy
:返回符合条件的属性
var isUpperCase = (val, key) => key.toUpperCase() === key; R.pickBy(isUpperCase)({a: 1, b: 2, A: 3, B: 4}) // {A: 3, B: 4}
keys
:返回对象自身属性的属性名组成的新数组。
R.keys({a: 1, b: 2, c: 3}) // ['a', 'b', 'c']
keysIn
:返回对象自身的和继承的属性的属性名组成的新数组。
var F = function() { this.x = 'X'; }; F.prototype.y = 'Y'; var f = new F(); R.keysIn(f) // ['x', 'y']
values
:返回对象自身的属性的属性值组成的数组。
R.values({a: 1, b: 2, c: 3}); //=> [1, 2, 3]
valuesIn
:返回对象自身的和继承的属性的属性值组成的数组。
var F = function() { this.x = 'X'; }; F.prototype.y = 'Y'; var f = new F(); R.valuesIn(f) // ['X', 'Y']
invertObj
:将属性值和属性名互换。如果多个属性的属性值相同,只返回最后一个属性。
var raceResultsByFirstName = { first: 'alice', second: 'jake', third: 'alice', }; R.invertObj(raceResultsByFirstName) // {"alice": "third", "jake": "second"}
invert
:将属性值和属性名互换,每个属性值对应一个数组。
var raceResultsByFirstName = { first: 'alice', second: 'jake', third: 'alice', }; R.invert(raceResultsByFirstName) // { 'alice': ['first', 'third'], 'jake':['second'] }
7.4 对象的运算
prop
:返回对象的指定属性
R.prop('x')({x: 100}) // 100 R.prop('x')({}) // undefined
map
:对象的所有属性依次执行某个函数。
var double = x => x * 2; R.map(double)({x: 1, y: 2, z: 3}) // {x: 2, y: 4, z: 6}
mapObjIndexed
:与map
类似,但是会额外传入属性名和整个对象。
var values = { x: 1, y: 2, z: 3 }; var prependKeyAndDouble = (num, key, obj) => key + (num * 2); R.mapObjIndexed(prependKeyAndDouble)(values) // { x: 'x2', y: 'y4', z: 'z6' }
forEachObjIndexed
:每个属性依次执行给定函数,给定函数的参数分别是属性值和属性名,返回原对象。
var printKeyConcatValue = (value, key) => console.log(key + ':' + value); R.forEachObjIndexed(printKeyConcatValue)({x: 1, y: 2}) // {x: 1, y: 2} // logs x:1 // logs y:2
merge
:合并两个对象,如果有同名属性,后面的值会覆盖掉前面的值。
R.merge({ 'name': 'fred', 'age': 10 })({ 'age': 40 }) // { 'name': 'fred', 'age': 40 } var resetToDefault = R.merge(R.__, {x: 0}); resetToDefault({x: 5, y: 2}) // {x: 0, y: 2}
mergeWith
:合并两个对象,如果有同名属性,会使用指定的函数处理。
R.mergeWith( R.concat, { a: true, values: [10, 20] }, { b: true, values: [15, 35] } ); // { a: true, b: true, values: [10, 20, 15, 35] }
eqProps
:比较两个对象的指定属性是否相等。
var o1 = { a: 1, b: 2, c: 3, d: 4 }; var o2 = { a: 10, b: 20, c: 3, d: 40 }; R.eqProps('a', o1)(o2) // false R.eqProps('c', o1)(o2) // true
R.evolve
:对象的属性分别经过一组函数的处理,返回一个新对象。
var tomato = { firstName: ' Tomato ', data: {elapsed: 100, remaining: 1400}, id: 123 }; var transformations = { firstName: R.trim, lastName: R.trim, // 不会被调用 data: {elapsed: R.add(1), remaining: R.add(-1)} }; R.evolve(transformations)(tomato) // { // firstName: 'Tomato', // data: {elapsed: 101, remaining: 1399}, // id: 123 // }
7.5 复合对象
path
:取出数组中指定路径的值。
R.path(['a', 'b'], {a: {b: 2}}) // 2 R.path(['a', 'b'], {c: {b: 2}}) // undefined
pathEq
:返回指定路径的值符合条件的成员
var user1 = { address: { zipCode: 90210 } }; var user2 = { address: { zipCode: 55555 } }; var user3 = { name: 'Bob' }; var users = [ user1, user2, user3 ]; var isFamous = R.pathEq(['address', 'zipCode'], 90210); R.filter(isFamous)(users) // [ user1 ]
assocPath
:添加或改写指定路径的属性的值。
R.assocPath(['a', 'b', 'c'], 42)({a: {b: {c: 0}}}) // {a: {b: {c: 42}}} R.assocPath(['a', 'b', 'c'], 42)({a: 5}) // {a: {b: {c: 42}}}
(完)
ppxu 说:
学习~
2017年3月 9日 09:42 | # | 引用
Free-HYF 说:
multiply:返回两个值的差。 错了吧,应该是 “返回两个值的乘积”吧。
2017年3月 9日 09:46 | # | 引用
晚晴幽草轩 说:
支持柯里化的玩法,挺喜欢;学习了,感谢。
2017年3月 9日 10:20 | # | 引用
阮一峰 说:
@Free-HYF:
谢谢指出,已经改正了。
2017年3月 9日 10:34 | # | 引用
blackcater 说:
R.useWith(Math.pow, [decreaseOne, increaseOne])(3)(4) // 32
32是怎么算出来的?我没弄懂这个函数的作用!
2017年3月 9日 11:27 | # | 引用
业余草 说:
这篇内容挺长的,写的很详细!
总之:“Ramda 还有一个特点:所有方法都支持柯里化。”
2017年3月 9日 11:34 | # | 引用
余昊 说:
memoize是保存第一次的运行结果吧。
2017年3月 9日 16:57 | # | 引用
Jackslow 说:
2017年3月 9日 21:36 | # | 引用
Jackslow 说:
文章里面出现了4次 R.__ ,有必要说下用法吧…… http://ramdajs.com/docs/#__
对于不同参数,memoize保存的是第一次的运行结果,如果下次有相同参数传入被 memoizely的函数,会直接返回之前保存的结果。
R.keys(R); 一共有238个自有属性,英文好的还是看官方文档吧。阮老师翻译辛苦了。
2017年3月 9日 21:55 | # | 引用
16163.co 说:
这是一篇很彻底的干货。
2017年3月10日 13:01 | # | 引用
jone 说:
Javascript的发展势头好迅猛啊,订阅了几个网站,每周都会给我发邮件“必看,最新的xx.js库/框架”
2017年3月10日 15:16 | # | 引用
jone 说:
阮老师的业余时间是怎么安排的呀好奇,都是用来翻阅学习新知识吗,感觉涉及的范围好广
2017年3月10日 15:19 | # | 引用
bellchet58 说:
lodash现在已经有提供fp的支持,将参数反向输入,而且即使lodash中不是每个函数curry化,也可以结合partialright函数使用,所以其实也没差啦
2017年3月10日 16:46 | # | 引用
tabooc 说:
没感觉出来有太大的优势,如果只是说这样符合函数式写法他就是正确的话我无话可说。
2017年3月10日 16:56 | # | 引用
爱喝水的鸡 说:
右侧curry化的变态实现
2017年3月10日 20:51 | # | 引用
cshenger 说:
函数式的库感觉都差不多,掌握了一个其他的过一遍API也就会了
2017年3月10日 22:01 | # | 引用
gaojiren 说:
函数式编程有什么优势呢?我感觉这种写法调试起来可能会比较头痛吧
2017年3月11日 13:29 | # | 引用
ldj 说:
看到文章题目我在想:为什么参数位置不对就要用它呢?
2017年3月11日 19:29 | # | 引用
追随 说:
之前介绍函数式编程入门的文章里,参考链接中的第一条[JS 函数式编程指南](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/)可以学学,就知道了。
2017年3月13日 09:23 | # | 引用
张宝 说:
读了阮一峰老师的三篇关于函数式编程的文章,有很大收获。这篇文章比较长,我也只读到了 6.6 复合数组部分。
我发现了文章里代码错误的地方(逗号)、某些地方可能有更好的表达方式。记在下面:
1. 代码有两处的逗号使用中文的了
分别是
R.adjust(R.add(10),1)([1, 2, 3]) // [1, 12, 3]
和
R.intersectionWith(R.eqBy(R.prop('id')),buffaloSpringfield)(csny)
2. 两处函数解释上
解释如下,是否会更容易理解些。
takeWhile 取得满足条件的成员
dropWhile 过滤掉满足条件的成员
3. 函数命名上
var diff = function(a, b) { return a - b; };
R.sort(diff)([4,2,7,5])
// [2, 4, 5, 7]
写成
var byNumberAsc = function(a, b) { return a - b; };
R.sort(byNumberAsc)([4,2,7,5])
// [2, 4, 5, 7]
是不是更好些
2017年3月14日 16:27 | # | 引用
大头 说:
同感,没感到函数式编程带来的优势,为了函数式而函数式
2017年3月15日 16:36 | # | 引用
z 说:
安利一个网站,中文的,http://ramda.cn
2017年3月17日 10:35 | # | 引用
Min 说:
"takeWhile: 一旦满足条件,后面的成员都会被过滤。"
这里我觉得是一旦不满足条件吧。
2017年3月27日 17:44 | # | 引用
苏 说:
老师您好,说下我自己的看法。早期我自己手搓的一些api我也是习惯function first的,但是这样就带来了一些问题——
一些形如这样的函数就不怎么好看了。
```
func1(function () {
// code
// here
}, param1, param2)
```
如果把匿名函数放在后面则代码形状更为好看一些。
```
func2(param1, param2, function () {
// code
// here
})
```
至少我是真的考虑到代码形状才改变写法的,像我这样偏爱这样形状代码的应该不在少数。而参数顺序本身就是个无关紧要的东西,实在要规定的话,就当做是软件工程的产物吧。
2017年3月28日 05:05 | # | 引用
Min 说:
这样执行的:
Math.pow((3-1),(4+1))
2017年3月28日 09:58 | # | 引用
Qquanwei 说:
如果不用ramda,或者死守underscope或lodash 那么永远也不会感受到ramda的优雅。我用了一年的ramda,可以说几乎所有的逻辑操作都可以用ramda基础意义进行翻译,而且也会更加优雅,代码更加简洁,更加忍受不了没有curry的工具库,更加害怕有副作用的工具库函数。
2017年4月 9日 08:52 | # | 引用
sleeper 说:
R.gt(2)(1); 得出的結果是 ( 2 > 1 ) true
但這是 pointer free 的好處嗎?感覺不太直覺。意思是
var greaterThan2 = R.gt(2);
greaterThan2 (1) 為 true 這樣嗎?
感覺 R.gt(2)(1) 的意思應該是 (1>2) false
不然直接寫 R.gt(2, 1) 更清楚吧
2017年8月13日 08:13 | # | 引用
sleeper 说:
感覺 curry 應該優先結合右邊的引數比較合理。例如
var sub = (a, b) ==> a-b;
var cSub = R.curry(sub);
var cSub3 = cSub(3);
cSub3(5) 應該是 2 而不該是 -2
這樣的話 underscore 把 function 放後面才對。
依照 Ramda 的規則。function 應該定義成 var subBy = (a, b) ==> b-a;
var cSub3 = R.curry(subBy)(3);
後面用起來比較正常。但是定義過程很怪。
2017年8月13日 08:40 | # | 引用
sleeper 说:
感覺 curry 應該優先結合右邊的引數比較合理。例如
var sub = (a, b) ==> a-b;
var cSub = R.curry(sub);
var cSub3 = cSub(3);
cSub3(5) 應該是 2 而不該是 -2
這樣的話 underscore 把 function 放後面才對。
依照 Ramda 的規則。function 應該定義成 var subBy = (a, b) ==> b-a;
var cSub3 = R.curry(subBy)(3);
後面用起來比較正常。但是定義過程很怪。
2017年8月13日 09:02 | # | 引用
宋乐 说:
reduceRight:与reduce类似,区别是数组成员从左到右执行。
这里写错了,阮大大,是从右到左。
2017年8月24日 10:47 | # | 引用
loszer 说:
undersocre和lodash支持链式调用的
2017年10月10日 14:28 | # | 引用
exy 说:
groupBy:将数组成员依次按照指定条件两两比较,并按照结果将所有成员放入子数组... ...
这里的groupBy好像应该是groupWith;
reduceRight:与reduce类似,区别是数组成员从左到右执行。
这里好像应该是“从右到左执行”。
2017年10月15日 21:25 | # | 引用
ieq 说:
实际项目里过程式编程很容易写成一大坨啦,函数式能限制写法
1. 操作流清晰,可读性较好(尤其只想了解一段代码大意时)
2. 单元测试好测,单拎出每一步写测试即可
3. 各步骤操作都变成一个个函数了,可以作为库复用,自由组装
2017年11月 1日 19:57 | # | 引用
eric 说:
请问sortBy如何降序排序?
2017年11月28日 16:41 | # | 引用
菜鸟guoguo 说:
阮老师 比较运算里的 equals的运算结果应该是false
2018年4月 2日 15:19 | # | 引用
Lee001 说:
我感觉也是的。
2018年7月10日 12:39 | # | 引用
Koukouzaki 说:
感觉很多函数并没有什么卵用,例如:R.divide(0.28, 100),依旧需要自行处理。
2019年1月18日 17:11 | # | 引用
爱喝哇哈哈哈 说:
其实,这里的函数名跟作用都跟 RxJS 非常类似,例如 tap 是用来执行 sideEffect 的,takeWhile 执行第一个满足条件的元素,其余忽略都跟 RxJS 行为是一致的。JS 函数式,learn once,write any where
2019年7月23日 17:15 | # | 引用
futianshu 说:
在官网的手册(https://ramdajs.com/docs/)里没有查到intersectionWith这个方法,是因为版本不同了吗?
2019年8月21日 13:12 | # | 引用
没大名不称呼 说:
安利一个网站,中文的,http://ramda.cn
2019年10月30日 10:55 | # | 引用
哈哈哈 说:
groupBy 这个地方好像写成了groupWith,两两比较这个有点怪,好像有误
2019年12月 2日 17:24 | # | 引用
a02418 说:
takeWhile 是一旦不满足条件,后续成员会被过滤
reduceRight 是从右到左进行迭代,而且累积变量是作为第二个参数传进去的
2019年12月24日 16:21 | # | 引用
webrabbit 说:
同感
2021年1月26日 10:09 | # | 引用
hd_mysky 说:
`intersectionWith` 已更新为`innerJoin`,使用方式也有改变,详见 https://ramdajs.com/docs/#innerJoin
2021年6月15日 12:02 | # | 引用
hersummer 说:
reduceRight 这个与reduce相比,区别应该是数组成员从右到左执行的
2021年9月30日 13:53 | # | 引用
wind 说:
感觉ramda函数库中很多函数的应用场景很有限。另外一个就是,函数库中的实现有很多都可以使用现在的es6语法稍加处理就能得到了,我感觉没必要为此记忆这些东西。也就是其实不需要记忆那么多函数。我的想法是,吸收一些他实现的比较优秀的对自己有帮助的函数,并封装到自己常用的js库中。这样既去除了哪些使用频率低的函数。又扩充了自己的函数库。根据实际不同的项目做一些函数库的增减。
最后就是这个东西在团队推行的难度在于--->难以保证团队成员都接受这种写法。
2021年11月 7日 20:25 | # | 引用