【发布时间】:2012-02-16 11:04:28
【问题描述】:
在 CoffeeScript 中构建类时,是否应该使用 =>(“胖箭头”)运算符定义所有实例方法,使用 -> 运算符定义所有静态方法?
【问题讨论】:
-
你能发布一些示例代码吗?
标签: coffeescript arrow-functions
在 CoffeeScript 中构建类时,是否应该使用 =>(“胖箭头”)运算符定义所有实例方法,使用 -> 运算符定义所有静态方法?
【问题讨论】:
标签: coffeescript arrow-functions
不,这不是我会使用的规则。
我在定义方法时发现的胖箭头的主要用例是当您想将方法用作回调并且该方法引用实例字段时:
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 的一个赋值之间进行选择,那么同时使用这两个变量的最佳方法是什么?
在其他答案中没有提到的重要一点是,在不必要的情况下使用粗箭头绑定函数可能会导致意外结果,例如在这个示例中我们将调用 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
因此,何时使用细/粗箭头可以很容易地总结为四种方式:
当满足这两个条件时,应使用单独的细箭头函数:
当满足以下条件时,应使用单独的粗箭头函数:
当满足以下条件时,应使用直接调用细箭头函数的粗箭头函数:
当满足以下条件时,应使用直接调用粗箭头(未演示)函数的细箭头函数:
在所有方法中,必须考虑原型函数可能会改变的情况,无论特定实例的行为是否正确,例如,虽然函数是用粗箭头定义的,但它的行为在实例中可能不一致如果它调用了在原型中更改的方法
【讨论】:
通常,-> 可以。
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!
=>。
// is not a CoffeeScript comment 而# is a CoffeeScript comment。
setTimeout foo.bar, 1000“做错了什么”?使用粗箭头比使用 setTimeout (-> foo.bar()), 1000 恕我直言要好得多。
setTimeout 中有这种语法的情况。但是您的第一条评论有些做作,并没有揭示合法的用例,而只是揭示了它可能会如何破坏。我只是说你不应该使用=>,除非你有充分的理由需要它,尤其是在类实例方法上,因为创建一个需要在实例化时绑定的新函数会带来性能成本。
只是一个例子来解释粗箭头
不起作用:(@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
【讨论】: