【问题标题】:Problems understading the translation of thenBy.js from JavaScript to CoffeeScript理解 thenBy.js 从 JavaScript 到 CoffeeScript 的翻译的问题
【发布时间】:2025-12-15 12:55:02
【问题描述】:

我需要在 CoffeeScript 中进行一些高级数组排序,我遇到了 thenBy.js 微型库,它完全符合我的需求。它是用 JavaScript 编写的,所以我将它翻译成 CoffeeScript,以便我可以在我的 .coffee 文件中内联使用它,但我在翻译时遇到了一些问题。这不起作用:

firstBy = ->

  # mixin for the `thenBy` property
  extend = (f) ->
    f.thenBy = tb
    return f

  # adds a secondary compare function to the target function (`this` context)
  #which is applied in case the first one returns 0 (equal)
  #returns a new compare function, which has a `thenBy` method as well
  tb = (y) ->
    x = this
    return extend((a, b) ->
      return x(a, b) or y(a, b)
    )
  return extend

但是,如果我用括号括起来并放在尾随括号上,它确实有效:

### Notice the starting paren
firstBy = (->

  # mixin for the `thenBy` property
  extend = (f) ->
    f.thenBy = tb
    return f

  # adds a secondary compare function to the target function (`this` context)
  #which is applied in case the first one returns 0 (equal)
  #returns a new compare function, which has a `thenBy` method as well
  tb = (y) ->
    x = this
    return extend((a, b) ->
      return x(a, b) or y(a, b)
    )
  return extend
)() ### <- Notice the ending parens

我无法理解为什么将这些尾随括号放在事物上会使其工作。我知道我有一个匿名函数,然后我用这些括号 (see this answer) 调用它,但为什么会这样呢?

【问题讨论】:

  • 两个版本都可以编译成 JS 看看有什么不同吗?
  • 是的。它们都编译得很好,括号是两者在输出 JS 和输入中的唯一区别。这真的只是我不明白这个聪明的小 sn-p 是如何实际工作的。为什么在定义它时必须调用它才能使其工作?
  • 您能否尝试仅在第一个示例中使用=&gt; 而不是-&gt; 用于extendtb,让我知道这是否适合您?

标签: javascript coffeescript


【解决方案1】:

后面的括号正在调用函数更大的函数(在第一个示例中定义为 firstBy 的函数),有效地将变量设置为函数的返回值:扩展函数。换句话说:

# Let firstBy1 be equivalent to your first definition, and
# firstBy2 equivalent to your second. Then
# firstBy1() is functionally equivalent to firstBy2

console.log firstBy2
### logs:
#   function (f) {
#     f.thenBy = tb
#     return f
### }

# So:
func = (x, y) ->
  # ... some comparison of x and y...

foo = firstBy2(func) # or `foo = firstBy1()(func)`
console.log foo.thenBy
### logs:
#   function (y) {
#     x = this
#     return extend(function (a, b) {
#       return x(a, b) or y(a, b)
#     })
### }

这可能最好用一个例子来说明:

# For brevity/clarity...
firstBy = firstBy2

randomNums =
  { attr1: 1, attr2: 2 },
  { attr1: 2, attr2: 8 },
  { attr1: 4, attr2: 2 },
  { attr1: 5, attr2: 2 },
  { attr1: 5, attr2: 3 },
  { attr1: 6, attr2: 1 },
  { attr1: 3, attr2: 1 },
  { attr1: 2, attr2: 4 }

func1 = (x, y) ->
  if x.attr1 == y.attr1
    return 0
  if x.attr1 > y.attr1 then 1 else -1

func2 = (x, y) ->
  if x.attr2 == y.attr2
    return 0
  if x.attr2 > y.attr2 then 1 else -1

当我们打电话时...

randomNums.sort(firstBy(func1).thenBy(func2))

...firstBy(func1) 首先被评估。它返回带有 thenBy 属性的 func1:

func1 = (x, y) -> 
  if x.attr1 == y.attr1
    return 0
  if x.attr1 > y.attr1 then 1 else -1

func1.thenBy = (y) ->
  x = this
  return extend((a, b) ->
    return x(a, b) or y(a, b)
  )

那么,调用 firstBy(func1).thenBy(func2) 调用新添加的 thenBy 附加到 func1 的参数,产生:

func1.thenBy(func2) =
  extend((a, b) ->
    func1(a, b) or func2(a, b)
  )

然后,extend 函数将另一个(未调用的)thenBy 属性应用于此匿名函数,产生 sort 用于对 randomNums 排序的最终函数。它基本上是在数组中的每对数字上调用 func1,如果 func1 返回 0(当每个对象的 attr1 相等时),则在同一对上计算 func2。这可以通过对 thenBy 的更多调用来无休止地推断。最后,我们的函数调用返回:

[
  { attr1: 1, attr2: 2 },
  { attr1: 2, attr2: 4 },
  { attr1: 2, attr2: 8 },
  { attr1: 3, attr2: 1 },
  { attr1: 4, attr2: 2 },
  { attr1: 5, attr2: 2 },
  { attr1: 5, attr2: 3 },
  { attr1: 6, attr2: 1 }
]

希望有帮助!

【讨论】:

  • 啊,原来如此,这就是启用流畅API机制的原因,即firstBy(???).thenBy(???).thenBy(???).etc?
  • 是的,因为 firstBy 和 thenBy 都返回内部扩展函数的实例。这段代码令人沮丧地递归,为了清楚起见,我将在帖子中举例说明。
  • 还值得注意的是,您可以使用 do 关键字获得相同的功能!不要在开头使用(,在结尾使用)(),您可以将其更改为firstBy = do -&gt;...,这将调用该函数。你也不需要明确告诉 CoffeeScript 给 return 任何东西。