在 CoffeeScript 中构建类时,是否应该使用=>
(“胖箭头”)运算符定义所有实例方法,并使用运算符定义所有静态方法->
?
4 回答
不,这不是我会使用的规则。
我在定义方法时发现的胖箭头的主要用例是当您想将方法用作回调并且该方法引用实例字段时:
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
或调用其他实例方法一样。最后一行是针对使用细箭头的情况的解决方法。
其他答案中没有提到的重要一点是,在不需要时使用粗箭头绑定函数可能会导致意外结果,例如在这个示例中,我们将调用 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
因此,何时使用细/粗箭头可以很容易地总结为四种方式:
当满足这两个条件时,应使用单独的细箭头函数:
- 该方法永远不会通过引用传递,包括 event_handlers 例如,您永远不会遇到以下情况: some_reference = some_instance.some_method; 一些参考()
- 并且该方法应该在所有实例中都是通用的,因此如果原型函数发生更改,那么该方法在所有实例中也会发生变化
当满足以下条件时,应使用单独的胖箭头函数:
- 该方法应在实例创建时精确绑定到实例,并且即使原型的函数定义发生更改,也应保持永久绑定,这包括函数应为事件处理程序且事件处理程序行为应一致的所有情况
当满足以下条件时,应使用直接调用细箭头函数的胖箭头函数:
- 该方法需要通过引用来调用,例如事件处理程序
- 并且功能可能会在未来通过替换细箭头功能来影响现有实例
当满足以下条件时,应使用直接调用粗箭头(未演示)函数的细箭头函数:
- 胖箭头函数必须始终附加到实例
- 但是细箭头功能可能会改变(即使是不使用原始粗箭头功能的新功能)
- 并且永远不需要通过引用传递细箭头函数
在所有方法中,必须考虑原型函数可能会改变的情况,无论特定实例的行为是否正确,例如,尽管函数是用粗箭头定义的,但如果它调用,它的行为在实例中可能不一致在原型中更改的方法
通常,->
很好。
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()
在这两种情况下,使用粗箭头声明该函数将允许它们工作。但除非你做一些奇怪的事情,否则你通常不需要这样做。
所以->
在你真正需要之前使用,=>
并且不要=>
默认使用。
只是一个理解胖箭头的例子
不起作用:(@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