【问题标题】:CoffeeScript, When to use fat arrow (=>) over arrow (->) and vice versaCoffeeScript,何时在箭头 (->) 上使用粗箭头 (=>),反之亦然
【发布时间】:2012-02-16 11:04:28
【问题描述】:

在 CoffeeScript 中构建类时,是否应该使用 =>(“胖箭头”)运算符定义所有实例方法,使用 -> 运算符定义所有静态方法?

【问题讨论】:

标签: coffeescript arrow-functions


【解决方案1】:

不,这不是我会使用的规则。

我在定义方法时发现的胖箭头的主要用例是当您想将方法用作回调并且该方法引用实例字段时:

class A
  constructor: (@msg) ->
  thin: -> alert @msg
  fat:  => alert @msg

x = new A("yo")
x.thin() #alerts "yo"
x.fat()  #alerts "yo"

fn = (callback) -> callback()

fn(x.thin) #alerts "undefined"
fn(x.fat)  #alerts "yo"
fn(-> x.thin()) #alerts "yo"

如您所见,如果您不使用粗箭头,则可能会遇到将实例方法的引用作为回调传递的问题。这是因为胖箭头将对象的实例绑定到this,而细箭头没有,因此上述作为回调调用的细箭头方法无法访问实例的字段,如 @msg 或调用其他实例方法。最后一行是针对使用细箭头的情况的解决方法。

【讨论】:

  • 当您想使用从细箭头调用的this 以及通过粗箭头获得的实例变量时,您会怎么做?
  • 正如我所说的“最后一行有一个解决方法,用于解决使用细箭头的情况。”
  • 我想你误解了我的问题。假设回调的默认范围将this 设置为我要使用的变量。但是,我也想引用一个类方法,所以我希望this 也引用这个类。我只能在 this 的一个赋值之间进行选择,那么同时使用这两个变量的最佳方法是什么?
  • @AndrewMao 你应该在这个网站上发布一个完整的问题,而不是让我在评论中回答:)
  • 没关系,问题没那么重要。但我只是想澄清一下,这不是您在最后一行代码中所指的内容。
【解决方案2】:

在其他答案中没有提到的重要一点是,在不必要的情况下使用粗箭头绑定函数可能会导致意外结果,例如在这个示例中我们将调用 DummyClass 的类。

class DummyClass
    constructor : () ->
    some_function : () ->
        return "some_function"

    other_function : () =>
        return "other_function"

dummy = new DummyClass()
dummy.some_function() == "some_function"     # true
dummy.other_function() == "other_function"   # true

在这种情况下,函数完全符合人们的预期,使用粗箭头似乎没有任何损失,但是当我们在 DummyClass 原型已经定义后修改它时会发生什么(例如更改一些警报或更改日志):

DummyClass::some_function = ->
    return "some_new_function"

DummyClass::other_function = ->
    return "other_new_function"

dummy.some_function() == "some_new_function"   # true
dummy.other_function() == "other_new_function" # false
dummy.other_function() == "other_function"     # true

正如我们所见,覆盖我们之前定义的原型函数会导致 some_function 被正确覆盖,但 other_function 在实例上保持不变,因为胖箭头导致类中的 other_function 绑定到所有实例,因此实例不会引用到他们的班级找函数

DummyClass::other_function = =>
    return "new_other_new_function"

dummy.other_function() == "new_other_new_function"    # false

second_dummy = new DummyClass()
second_dummy.other_function() == "new_other_new_function"   # true

即使是粗箭头也不会起作用,因为粗箭头只会使函数绑定到新实例(它确实获得了预期的新函数)。

但是这会导致一些问题,如果我们需要一个可以在所有现有实例(包括事件处理程序)上工作的函数(例如,在将日志记录函数切换到输出框或其他东西的情况下)[这样我们可以'不使用原始定义中的粗箭头],但我们仍然需要访问事件处理程序中的内部属性[我们使用粗箭头而不是细箭头的确切原因]。

实现这一点最简单的方法是在原始类定义中仅包含两个函数,一个用细箭头定义,它执行您希望执行的操作,另一个用粗箭头定义,除了调用之外什么都不做例如第一个函数:

class SomeClass
    constructor : () ->
        @data = 0
    _do_something : () ->
        return @data
    do_something : () =>
        @_do_something()

something = new SomeClass()
something.do_something() == 0     # true
event_handler = something.do_something
event_handler() == 0              # true

SomeClass::_do_something = -> return @data + 1

something.do_something() == 1     # true
event_handler() == 1              # true

因此,何时使用细/粗箭头可以很容易地总结为四种方式:

  1. 当满足这两个条件时,应使用单独的细箭头函数:

    • 该方法永远不会通过引用传递,包括 event_handlers,例如你从来没有这样的案例: some_reference = some_instance.some_method; some_reference()
    • 并且该方法应该在所有实例中都是通用的,因此如果原型函数发生变化,那么该方法在所有实例中也会发生变化
  2. 当满足以下条件时,应使用单独的粗箭头函数:

    • 该方法应在实例创建时精确绑定到实例,并且即使原型的函数定义发生更改,也应保持永久绑定,这包括函数应为事件处理程序且事件处理程序行为应一致的所有情况
  3. 当满足以下条件时,应使用直接调用细箭头函数的粗箭头函数:

    • 该方法需要通过引用来调用,例如事件处理程序
    • 并且功能可能会在未来发生变化,通过替换细箭头功能影响现有实例
  4. 当满足以下条件时,应使用直接调用粗箭头(未演示)函数的细箭头函数:

    • 胖箭头函数必须始终附加到实例
    • 但是细箭头函数可能会改变(甚至是不使用原始粗箭头函数的新函数)
    • 并且永远不需要通过引用传递细箭头函数

在所有方法中,必须考虑原型函数可能会改变的情况,无论特定实例的行为是否正确,例如,虽然函数是用粗箭头定义的,但它的行为在实例中可能不一致如果它调用了在原型中更改的方法

【讨论】:

    【解决方案3】:

    通常,-> 可以。

    class Foo
      @static:  -> this
      instance: -> this
    
    alert Foo.static() == Foo # true
    
    obj = new Foo()
    alert obj.instance() == obj # true
    

    注意静态方法如何返回this 的类对象,而实例返回this 的实例对象。

    发生的情况是调用语法提供了this 的值。在这段代码中:

    foo.bar()
    

    foo 默认是bar() 函数的上下文。所以它只是按照你想要的方式工作。当您以不使用点语法的其他方式调用这些函数时,您只需要粗箭头。

    # Pass in a function reference to be called later
    # Then later, its called without the dot syntax, causing `this` to be lost
    setTimeout foo.bar, 1000
    
    # Breaking off a function reference will lose it's `this` too.
    fn = foo.bar
    fn()
    

    在这两种情况下,使用粗箭头声明该函数将允许它们工作。但除非你做一些奇怪的事情,否则你通常不需要这样做。

    所以在你真正需要=>之前使用->,并且不要默认使用=>

    【讨论】:

    • 如果你这样做会失败:x = obj.instance; alert x() == obj # false!
    • 当然会,但这属于“做错事”。我现在已经编辑了我的答案并解释了何时需要在类的静态/实例方法上使用 =>
    • Nitpick:// is not a CoffeeScript comment# is a CoffeeScript comment
    • setTimeout foo.bar, 1000“做错了什么”?使用粗箭头比使用 setTimeout (-> foo.bar()), 1000 恕我直言要好得多。
    • @nicolaskruchten 当然,setTimeout 中有这种语法的情况。但是您的第一条评论有些做作,并没有揭示合法的用例,而只是揭示了它可能会如何破坏。我只是说你不应该使用=>,除非你有充分的理由需要它,尤其是在类实例方法上,因为创建一个需要在实例化时绑定的新函数会带来性能成本。
    【解决方案4】:

    只是一个例子来解释粗箭头

    不起作用:(@canvas 未定义)

    class Test
      constructor: ->
        @canvas = document.createElement 'canvas'
        window.addEventListener 'resize', ->
          @canvas.width = window.innerWidth
          @canvas.height = window.innerHeight
    

    作品:(@canvas 定义)

    class Test
      constructor: ->
        @canvas = document.createElement 'canvas'
        window.addEventListener 'resize', =>
          @canvas.width = window.innerWidth
          @canvas.height = window.innerHeight
    

    【讨论】:

      猜你喜欢
      • 2016-01-25
      • 2011-10-19
      • 2012-09-20
      • 1970-01-01
      • 1970-01-01
      • 2017-12-18
      • 2016-08-01
      • 2019-06-25
      • 1970-01-01
      相关资源
      最近更新 更多