CoffeeScript,何时在箭头(- >)上使用胖箭头(= >) ,反之亦然

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

33141 次浏览

不,我不会用这个规则。

我发现胖箭头在定义方法时的主要用例是当你想使用一个方法作为回调时,该方法引用实例字段:

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或者调用其他实例方法。最后一行是针对使用了瘦箭头的情况的解决方案。

通常 ->是正常的。

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()

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

因此,在真正需要 =>之前使用 ->,并且在默认情况下永远不要使用 =>

在其他答案中没有提到的一点是,当不需要使用胖箭头绑定函数时,可能会导致意外的结果,例如在本例中,使用一个我们称为 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

正如我们可以看到的那样,覆盖我们先前定义的原型函数会导致一些 _ 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. 当满足下列条件时,应使用直接调用瘦箭头函数的胖箭头函数:

    • 需要通过引用(如事件处理程序)调用该方法
    • 在将来,通过替换瘦箭头函数,AND 的功能可能会改变,从而影响现有的实例
  4. 当满足下列条件时,应使用直接调用脂肪箭头(未演示)函数的细箭头函数:

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

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

只是理解胖箭头的一个例子

非作品: (@画布未定义)

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

作品: (@画布定义)

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