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

Sim*_*olm 12 memory-management coffeescript arrow-functions

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

编辑:那好吧!很好的回复!:)
总而言之,问题是:
- 需要更多内存
- 无法修补
- 请问问题,为什么它用于此方法?
惯例:
- 绑定功能时要明确.
- 在构造函数中声明胖箭头方法.
- 尽可能多地使用,而不是在类声明中.

epi*_*ian 18

是的,总有理由不使用胖箭.事实上,我赞成永远不要使用胖箭的方法:)

细箭和胖箭方法在概念上是不同的.前者被编译为预期的基于原型的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
Run Code Online (Sandbox Code Playgroud)

但是我认为最重要的缺点是更主观:引入胖箭头方法会使代码(和语言)不必要地不一致并且难以理解.

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

还有另一个不一致之处:胖箭头在方法定义中使用时的绑定方式与在this其他任何地方使用时绑定的方式不同.代替保持外的this(其内的class,this结合于类构造函数)的this一个脂肪箭头方法内是当该方法被定义(即一个类的实例),其不存在的对象.

如果你混合使用精简箭头和胖箭头的方法,代码也变得更难以遵循,因为现在每次开发人员看到一个胖箭头的方法时,他们会问自己为什么需要方法实例绑定.方法的声明与其使用位置之间没有直接关联,这是需要fat-arrow方法的地方.


尽管如此,我建议永远不要使用胖箭方法.首选将方法绑定到将要使用它的实例,而不是声明方法的位置.例如:

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

# Instead of:
$someJQueryElement.on 'click', someObject.onClick
Run Code Online (Sandbox Code Playgroud)

或者,如果您真的想在构造时绑定每个实例上的方法,请明确说明:

# 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
Run Code Online (Sandbox Code Playgroud)

我认为在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
Run Code Online (Sandbox Code Playgroud)


Tob*_*bia 12

让我添加我的替代视图.

@epidemian表达的避免胖箭的精心理由很好,但请考虑以下因素:

  • 如果您不关心(很多或根本没有)关于CoffeeScript生成的"基于原型的JS代码",只要您能够编写一致且无错误的CoffeeScript代码;
  • 如果你不打算在Java上编写大量的小类,那将花99%的时间在继承树上上下调用彼此的方法,并在这个过程中完成很少的工作; 换句话说,如果你认识到性能敏感的"内部循环"不是放置方法调用的好地方;
  • 如果你不计划在运行时进行装饰,猴子修补或以其他方式修改类的方法;
  • 如果你在标题注释中陈述你对胖箭头的使用,为了未来开发人员的代码的利益;

那么我建议总是使用胖箭作为一种习惯,无论是方法还是匿名函数.

这将使您的CoffeeScript代码更简单,更安全,更直观,因为您将了解this@始终引用您正在定义其方法的当前对象,就像在大多数其他编程语言中一样,无论谁将在运行时调用您的函数和方法.

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

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

注意:CoffeeScript 1.6.1有一个与胖箭头方法相关的错误,所以你应该避免这种情况.以前版本和更高版本应该没问题.

性能

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

恕我直言,语言清晰度和安全性脂肪箭交换是足以容忍甚至欢迎小开销的理由.许多其他CoffeeScript习语为生成的代码(for循环等)添加了自己的微小开销,目的是使语言行为更加一致并且不易出错.胖箭没有什么不同.

  • @owensmartin,欢迎你.事实上,我通常将我的回调定义为fat-arrow方法,并带有描述性名称,以便我可以将它们作为值传递给库:`someLibrary.doSomething 1,2,@ somethingWasDone`这会将我的代码转换为线性序列方法调用,没有嵌套` - >`的金字塔和相关的变量范围问题. (2认同)