在coffeescript中构建类时,是否有理由不对实例方法使用胖箭头



在coffeescript中构建类时,是否有理由使用胖箭头作为实例方法?

编辑:好吧!很好的回复!:)
总之,问题是:
-占用更多内存
--无法进行修补
-问题是,为什么要将其用于此方法
约定:
-绑定函数时要明确
-在构造函数中声明胖箭头方法
-可以随心所欲地使用,只是不要在类声明中使用

是的,不总是使用胖箭头是有原因的。事实上,我主张永远不要使用肥箭头方法:)

瘦箭头和胖箭头方法在概念上是不同的。前者被编译成预期的基于原型的JS代码;这些方法属于类原型。另一方面,胖箭头方法与构造函数代码中的每个实例相关联。

总是使用胖箭头方法最明显的缺点是,它会使每个类实例占用更多的内存(因为它有更多自己的属性),并且初始化速度较慢(因为它必须创建那些绑定函数,并在每次创建实例时设置它们)。

使用胖箭头方法的另一个缺点是,它打破了通常对方法的期望:方法不再是类实例之间共享的函数,但现在它是每个实例的单独函数。例如,如果在类中定义了一个方法后想要修改它,这可能会导致问题

class Foo
# Using fat-arrow method
bar: (x) => alert x
# I have some Foos
foos = (new Foo for i in [1..3])
# And i want to path the bar method to add some logging. 
# This might be in another module or file entirely.
oldbar = Foo::bar
Foo::bar = (args...) ->
console.log "Foo::bar called with", args
oldbar.apply @, args
# The console.log will never be called here because the bar method 
# has already been bound to each instance and was not modified by 
# the above's patch.
foo.bar(i) for foo, i in foos

但在我看来,最重要的缺点是更主观:引入胖箭头方法会使代码(和语言)不必要地不一致,难以理解。

代码变得更加不一致,因为在引入胖箭头方法之前,每当我们在类定义中看到<someProp>: <someVal>时,我们都知道它的意思是"在类的原型中声明一个名为<someProp>、值为<someVal>的属性"(除非<someProp> == 'constructor',这是一种特殊情况),<someVal>是数字还是函数都无关紧要,它只是原型中的一个属性。随着胖箭头方法的引入,我们现在有了另一个不必要的特殊情况:如果<someVal>是一个胖箭头函数,它将做与任何其他值完全不同的事情。

还有另一个不一致之处:胖箭头在方法定义中使用时与在其他任何地方使用时对this的绑定不同。与其保留外部this(在class内部,this绑定到类构造函数),胖箭头方法内部的this是在定义该方法时不存在的对象(即类的实例)。

如果混合使用瘦箭头和胖箭头方法,代码也会变得更难理解,因为现在每次开发人员看到胖箭头方法时,他们都会问自己,为什么需要将方法绑定到实例中。方法的声明和它的使用位置之间没有直接的相关性,这就是胖箭头方法的需求所在。


对于所有这些,我建议永远不要使用胖箭头方法。更喜欢将方法绑定到要使用它的实例,而不是声明方法的实例。例如:

# Be explicit about 'onClick' being called on 'someObject':
$someJQueryElement.on 'click', (e) -> someObject.onClick e
# Instead of:
$someJQueryElement.on 'click', someObject.onClick

或者,如果您真的想在构建时将方法绑定到每个实例上,请明确这一点:

# Instead of fat-arrow methods:
class A
constructor: ->
@bar = 42
foo: => 
console.log @bar
# Assing the method in the constructor, just like you would 
# do with any other own property
class A
constructor: ->
@bar = 42
@foo = => 
console.log @bar

我认为在class A的第二个定义中,foo方法所发生的事情比第一个定义中要明确得多。

最后,请注意,我一点也不反对使用胖箭头。这是一个非常有用的构造,我一直在用它来实现正常函数;我只是更喜欢避免在class方法定义中使用它:)


编辑:另一个反对使用胖箭头方法的案例:装饰器函数:

# A decorator function to profile another function.
profiled = (fn) ->
(args...) ->
console.profile()
fn.apply @, args
console.profileEnd()
class A
bar: 10
# This works as expected
foo: profiled (baz) ->
console.log "@bar + baz:", @bar + baz
# This doesn't
fatArrowedFoo: profiled (baz) =>
console.log "@bar + baz:", @bar + baz
(new A).foo 5           # -> @bar + baz: 15
(new A).fatArrowedFoo 5 # -> @bar + baz: NaN

让我添加我的替代视图。

@epidemian表达的避免脂肪箭的详细原因是好的,但要考虑以下几点:

  • 如果您不关心(非常或根本不关心)CoffeeScript生成的"基于底层原型的JS代码",只要您能够编写一致且无错误的CoffeeSript代码
  • 如果你不打算在Java中编写大量的小类,它们将花费99%的时间在继承树上下调用彼此的方法,并且在这个过程中几乎没有完成任何工作;换句话说,如果您认识到性能敏感的"内部循环"不是放置方法调用的好地方
  • 如果您不打算在运行时装饰、修补或以其他方式修改类的方法
  • 如果你在标题注释中说明你对胖箭头的使用,这对未来开发你的代码有好处

那么我建议对于方法和匿名函数,始终使用胖箭头作为一种习惯。

这将使您的CoffeeScript代码更简单、更安全、更直观,因为您会知道this@总是引用您正在定义其方法的当前对象,就像在大多数其他编程语言中一样,与在运行时调用您的函数和方法的人无关。

更正式地说,胖箭头使this关键字(及其缩写@)与任何其他标识符一样,具有完全的词汇范围。编程语言历史表明,词法范围界定是范围标识符最直观、不易出错的方法。这就是为什么它在很久以前就成为所有新语言的标准行为。

如果选择此路径,细箭头将成为例外,并且是一个有用的路径。您将使用它来准备那些特定的回调,在这些回调中,您需要this来引用调用者在运行时定义的东西,而不是您自己的对象。这是this的反直觉含义,但一些JS库在用户提供的函数中需要这种行为。然后细箭头将用于突出显示这些代码片段。如果我没记错的话,jQuery通常在函数参数中提供所需的一切,所以你可以忽略它的人工this,但其他库就没有那么仁慈了。

注意:CoffeeScript 1.6.1有一个与胖箭头方法有关的错误,所以应该避免。以前和以后的版本应该可以。

性能

当用作常规(匿名)函数时,胖箭头不会增加任何开销。在方法声明中,它确实添加了微小的RAM和CPU开销(真的很小:每个方法调用几纳秒和几字节的RAM,后者在尾部调用优化的引擎上消失了。)

IMHO的语言清晰度和安全性脂肪箭给予的交换是足够容忍甚至欢迎小开销的理由。许多其他CoffeeScript习惯用法为生成的代码(用于循环等)添加了自己的微小开销,目的是使语言行为更加一致,不易出错。胖箭也不例外。

最新更新