免责声明:我是Ramda的创始人和主要维护者之一,所以我相信这个答案会包含一些个人偏见。我会尽力控制好不让它失控。
关于问题的这部分:
哪一个更好?
没有人能够明确地回答这个问题。这取决于你的需求、历史、编程口味和无数更加模糊的因素。
当然,我对Ramda有偏见,但我仍然不会告诉你哪一个更好。
但是这两者在功能上有显著的重叠,在基本哲学上有显著的差异。
它们都是实用函数的抓包,函数之间几乎没有任何连贯性。也就是说,它们是库而不是框架。它们根本不试图确定你如何编写或组织代码。
它们有很多重叠。在305个当前lodash函数和261个当前Ramda函数中,有103个共享名称,而且几乎总是具有类似的目的。剩下的函数可能有一半可以在其他库中找到,但是名称不同。
尽管在它之前有一些实验性的函数库,但Underscore是将许多这些概念带入Javascript主流的一个库。它曾经独自站立一段时间,成为这个领域中无可争议的领导者。
但是它的性能存在问题。最终lodash在很大程度上被创建出来是为了用更好的性能做同样的事情。通过创建自定义版本的经典函数,例如map
,而不是默认使用JS引擎内置的函数,lodash很快就能够胜过Underscore。但它最初是作为Underscore的替代品而开始的,并且长时间保持着这种关注点。
Ramda的创始人们对Reginald Braithwaite的JavaScript Allongé中的思想印象深刻,并创建了Ramda作为教育工具,以帮助将这些思想转化为实用的库。他们更关注干净的API和函数组合,以及不可变数据和无副作用编码,而不是性能。通过一系列奇怪的事件,它最终变得非常受欢迎。
这些天你很少听到Underscore的消息。它仍然存在,并且仍然被广泛使用,但是你不会听到太多关于它的热议。对于大多数用户来说,lodash填补了这个空间。Ramda增长迅速,但似乎已经稳定在比lodash低20-25%的下载量。但是这三个库在功能和使用方面继续增长,Underscore和Ramda的使用情况相同,远低于lodash。filter
:它接受数组、对象或字符串作为其集合,函数、对象、字符串或什么都不需要作为其回调,以及对象或什么都不需要作为其thisArg
。你得到了一个包含24个函数的组合!filter
函数接受一个谓词函数和一个可过滤类型的对象,并返回另一个该类型的对象。(当然,这引发了什么构成可过滤类型的问题,但这是在其文档中解决的。)Lodash (ignoring lodash-fp) functions mostly take their data first, followed by those things that work on the data, followed sometimes by optional arguments that change the behavior. Ramda puts the arguments least likely to change first, and those most likely to change last. This means that in data-transformation functions the data is last. And Ramda avoid optional arguments altogether.
Ramda curries all its functions, as well as nearly all the functions it returns to you. Lodash has a curry
function, but you would need to call it explicitly. This is fairly central to Ramda's ideas on function composition.
Lodash focuses on reference-equality. Ramda focuses on value-equality. So while these are similar:
// lodash
_.union ([1, 2, 3, 4, 5], [2, 3, 5, 7, 11]) //=> [1, 2, 3, 4, 5, 7, 11]
_.intersection ([1, 2, 3, 4, 5], [2, 3, 5, 7, 11]) //=> [2, 3, 5]
// Ramda
R.union ([1, 2, 3, 4, 5], [2, 3, 5, 7, 11]) //=> [1, 2, 3, 4, 5, 7, 11]
R.intersection ([1, 2, 3, 4, 5], [2, 3, 5, 7, 11]) //=> [2, 3, 5]
these act very differently:
// lodash
_.union (
[{x: 1}, {x: 2}, {x: 3}, {x: 4}, {x: 5}],
[{x: 2}, {x: 3}, {x: 5}, {x: 7}, {x: 11}]
)
//=> [{x: 1}, {x: 2}, {x: 3}, {x: 4}, {x: 5}, {x: 2}, {x: 3}, {x: 5}, {x: 7}, {x: 11}]
_.intersection (
[{x: 1}, {x: 2}, {x: 3}, {x: 4}, {x: 5}],
[{x: 2}, {x: 3}, {x: 5}, {x: 7}, {x: 11}]
) //=> []
// Ramda
R.union (
[{x: 1}, {x: 2}, {x: 3}, {x: 4}, {x: 5}],
[{x: 2}, {x: 3}, {x: 5}, {x: 7}, {x: 11}]
)
//=> [{x: 1}, {x: 2}, {x: 3}, {x: 4}, {x: 5}, {x: 7}, {x: 11}]
R.intersection (
[{x: 1}, {x: 2}, {x: 3}, {x: 4}, {x: 5}],
[{x: 2}, {x: 3}, {x: 5}, {x: 7}, {x: 11}]
) //=> [x: 2}, {x: 3}, {x: 5}]
This difference is significant. Ramda's design is more closely aligned with functional systems, but it comes at a large performance price whenever equality-checking is involved. Lodash is perhaps two orders of magnitude faster than Ramda for such tasks.
Ramda is chiefly designed to build functions through composition, in short or long pipelines. Lodash is chiefly designed to work with imperative code. You can use either library for either job, but typical Ramda code might look like
const myFn = R.pipe (
R.fn1,
R.fn2 ('arg1', 'arg2'),
R.fn3 ('arg3'),
R.fn4
)
where the equivalent lodash code might look like
const myFn = (x, y) => {
const var1 = _.fn1 (x, y)
const var2 = _.fn2 (var1, 'arg1', 'arg2')
const var3 = _.fn3 (var2, 'arg3')
return _.fn4 (var3)
}
Lodash's focus on performance means that it will supply many well-optimized functions for tasks. For instance, Lodash has all of these functions: isArguments
, isArray
, isArrayBuffer
, isArrayLike
, isArrayLikeObject
, isBoolean
, isBuffer
, isDate
, isElement
, isEqual
, isEqualWith
, isError
, isFinite
, isFunction
, isInteger
, isLength
, isMap
, isMatch
, isMatchWith
, isNaN
, isNative
, isNull
, isNumber
, isObject
, isObjectLike
, isPlainObject
, isRegExp
, isSafeInteger
, isSet
, isString
, isSymbol
, isTypedArray
, isUndefined
, isWeakMap
, isWeakSet
.
Ramda, by contrast expects you to supply more arguments to common functions. It has only is
, isEmpty
, and isNil
. But it's is
handles
nearly all the cases above, either by calling explicitly: is (Array, [1, 2, 3])
or by partially applying to make a reusable function const isArray = is (Array)
_.unionBy
和_.intersectionBy
可以得到与R.union
和R.intersection
相同的答案。 - foxirisunionWith
(我不确定为什么没有differenceWith
!)。 因此,R .unionWith (R .identical)
应该与_.union
行为相同。 区别在于库的哲学所认为的默认行为和期望行为,而不是你可以使用它们实现什么。 - Scott Sauyetlodash-fp
,但我并没有感觉它是整个编程领域的重要组成部分,而且看起来它正在逐渐消失。 - Scott Sauyet