CoffeeScript, quando usar a seta gorda (=>) sobre a seta (->) e vice-versa

133

Ao criar uma classe no CoffeeScript, todo o método de instância deve ser definido usando o =>operador ("seta gorda") e todos os métodos estáticos sendo definidos usando o ->operador?

Ali Salehi
fonte
Você pode postar algum código de exemplo?
sarnold
Veja também esta resposta stackoverflow.com/a/17431824/517371
Tobia

Respostas:

157

Não, essa não é a regra que eu usaria.

O principal caso de uso que encontrei para a seta gorda na definição de métodos é quando você deseja usar um método como retorno de chamada e esse método faz referência a campos de instância:

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"

Como você vê, você pode ter problemas ao passar uma referência ao método de uma instância como um retorno de chamada se você não usar a seta de gordura. Isso ocorre porque a seta gorda vincula a instância do objeto, ao thispasso que a seta fina não, então os métodos de seta fina chamados como retornos de chamada como acima não podem acessar os campos da instância @msgou chamar outros métodos de instância. A última linha existe uma solução alternativa para os casos em que a seta fina foi usada.

nicolaskruchten
fonte
2
O que você faz quando deseja usar o thisque seria chamado a partir da seta fina, mas também as variáveis ​​de instância que você obteria com a seta gorda?
Andrew Mao
Como eu disse "A última linha existe uma solução alternativa para os casos em que a seta fina foi usada".
Nicolaskruchten 23/09/12
Eu acho que você não entendeu minha pergunta. Suponha que o escopo padrão do retorno de chamada tenha sido thisdefinido como uma variável que eu quero usar. No entanto, também quero fazer referência a um método de classe, então também quero thisme referir à classe. Só posso escolher entre uma tarefa para this; então, qual é a melhor maneira de poder usar as duas variáveis?
Andrew Mao
@AndrewMao você provavelmente deve postar uma pergunta cheia neste site ao invés de ter me responder em um comentário :)
nicolaskruchten
Tudo bem, a pergunta não é tão importante. Mas eu só queria esclarecer que não era o que você estava se referindo na sua última linha de código.
Andrew Mao
13

Um ponto não mencionado em outras respostas que é importante observar é que as funções de ligação com a seta gorda quando não são necessárias podem levar a resultados indesejados, como neste exemplo com uma classe que chamaremos de 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

Nesse caso, as funções fazem exatamente o que se pode esperar e parece não haver perda no uso da seta gorda, mas o que acontece quando modificamos o protótipo DummyClass depois que ele já foi definido (por exemplo, alterar algum alerta ou alterar a saída de um log) :

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

Como podemos ver, substituir nossa função definida anteriormente do protótipo faz com que alguma função seja substituída corretamente, mas outra função permaneça a mesma nas instâncias, pois a seta gorda fez com que outra função da classe seja vinculada a todas as instâncias, para que as instâncias não se refiram à sua classe encontrar uma função

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

Mesmo a seta gorda não funcionará, uma vez que a seta gorda apenas faz com que a função seja vinculada a novas instâncias (que ganham as novas funções como seria de esperar).

No entanto, isso leva a alguns problemas, e se precisarmos de uma função (por exemplo, no caso de alternar uma função de log para uma caixa de saída ou algo assim) que funcione em todas as instâncias existentes (incluindo manipuladores de eventos) [como tal, não podemos usar setas gordas na definição original], mas ainda precisamos acessar atributos internos em um manipulador de eventos [o motivo exato pelo qual usamos setas gordas e não finas].

Bem, a maneira mais simples de conseguir isso é incluir apenas duas funções na definição de classe original, uma definida com uma seta fina que executa as operações que você deseja executar e outra definida com uma seta gorda que não faz nada além de chamar a primeira função por exemplo:

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

Portanto, quando usar flechas finas / gordas pode ser resumido facilmente de quatro maneiras:

  1. As funções de seta fina por si só devem ser usadas quando as duas condições forem atendidas:

    • O método nunca será passado por referência, incluindo event_handlers, por exemplo, você nunca terá um caso como: some_reference = some_instance.some_method; some_reference ()
    • E o método deve ser universal em todas as instâncias; portanto, se a função protótipo mudar, o método também ocorrerá em todas as instâncias.
  2. As funções de seta gorda sozinhas devem ser usadas quando a seguinte condição for atendida:

    • O método deve ser vinculado precisamente à instância na criação da instância e permanecer permanentemente vinculado, mesmo que a definição da função seja alterada para o protótipo, incluindo todos os casos em que a função deve ser um manipulador de eventos e o comportamento do manipulador de eventos deve ser consistente
  3. A função de seta gorda que chama diretamente uma função de seta fina deve ser usada quando as seguintes condições forem atendidas:

    • É necessário que o método seja chamado por referência, como um manipulador de eventos
    • E a funcionalidade pode mudar no futuro, afetando instâncias existentes, substituindo a função de seta fina
  4. A função de seta fina que chama diretamente uma função de seta gorda (não demonstrada) deve ser usada quando as seguintes condições forem atendidas:

    • A função de seta gorda deve estar sempre anexada à instância
    • MAS a função de seta fina pode mudar (mesmo para uma nova função que não usa a função de seta gorda original)
    • E a função de seta fina nunca precisa passar por referência

Em todas as abordagens, deve ser considerado no caso em que as funções do protótipo possam ser alteradas, se o comportamento de instâncias específicas se comportará ou não corretamente, por exemplo, embora uma função seja definida com uma seta gorda, seu comportamento pode não ser consistente em uma instância, se chamar um método que é alterado dentro do protótipo

Jamesernator
fonte
9

Geralmente, ->está bem.

class Foo
  @static:  -> this
  instance: -> this

alert Foo.static() == Foo # true

obj = new Foo()
alert obj.instance() == obj # true

Observe como o método estático retorna o objeto de classe thise a instância retorna o objeto de instância this.

O que está acontecendo é que a sintaxe de chamada está fornecendo o valor de this. Neste código:

foo.bar()

fooserá o contexto da bar()função por padrão. Então isso meio que funciona como você quer. Você só precisa da seta gorda quando chama essas funções de alguma outra maneira que não usa a sintaxe de pontos para invocação.

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

Nos dois casos, usar uma seta gorda para declarar essa função permitiria que eles funcionassem. Mas, a menos que você esteja fazendo algo estranho, geralmente não precisa.

Portanto, use ->até que você realmente precise =>e nunca use =>por padrão.

Alex Wayne
fonte
1
Isso falhará se você:x = obj.instance; alert x() == obj # false!
nicolaskruchten
2
Claro que sim, mas isso se encaixaria em "fazer errado". Agora editei minha resposta e expliquei quando =>seria necessário um método estático / instância de uma classe.
Alex Wayne
Nitpick: // is not a CoffeeScript commentconsiderando # is a CoffeeScript comment.
Nicolakruchten
Como está setTimeout foo.bar, 1000"fazendo errado"? Usar uma flecha gorda é muito melhor do que usar setTimeout (-> foo.bar()), 1000IMHO.
nicolaskruchten 23/01
1
@nicolaskruchten Há um argumento para essa sintaxe setTimeout, é claro. Mas seu primeiro comentário é um tanto artificial e não revela um caso de uso legítimo, mas simplesmente revela como ele pode se romper. Estou simplesmente dizendo que você não deve usar um a =>menos que precise dele por um bom motivo, especialmente em métodos de instância de classe em que ele tem um custo de desempenho para criar uma nova função que precisa estar vinculada à instanciação.
Alex Wayne
5

apenas um exemplo para destacar a seta gorda

não funciona: (@canvas undefined)

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

works: (@canvas definido)

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