CoffeeScript, quando utilizzare la freccia grassa (=>) sulla freccia (->) e viceversa


Risposte:


157

No, questa non è la regola che vorrei usare.

Il principale caso d'uso che ho trovato per la fat-arrow nella definizione dei metodi è quando vuoi usare un metodo come callback e quel metodo fa riferimento ai campi di istanza:

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"

Come vedi, potresti riscontrare problemi nel passare un riferimento al metodo di un'istanza come callback se non usi la fat-arrow. Questo perché la freccia grassa associa l'istanza dell'oggetto a thismentre la freccia sottile no, così i metodi a freccia sottile chiamati come callback come sopra non possono accedere ai campi dell'istanza come @msgo chiamare altri metodi di istanza. L'ultima riga è una soluzione alternativa per i casi in cui è stata utilizzata la freccia sottile.


2
Cosa fai quando vuoi usare quello thische verrebbe chiamato dalla freccia sottile, ma anche le variabili di istanza che otterrai con la freccia grassa?
Andrew Mao,

Come ho detto "L'ultima riga è una soluzione alternativa per i casi in cui è stata utilizzata la freccia sottile".
nicolaskruchten,

Penso che tu abbia frainteso la mia domanda. Supponiamo che l'ambito predefinito del callback sia thisimpostato su una variabile che voglio usare. Tuttavia, voglio anche fare riferimento a un metodo di classe, quindi desidero thisfare riferimento anche alla classe. Posso scegliere solo tra un compito per this, quindi qual è il modo migliore per essere in grado di utilizzare entrambe le variabili?
Andrew Mao,

@AndrewMao dovresti probabilmente pubblicare una domanda completa su questo sito piuttosto che farmi rispondere in un commento :)
nicolaskruchten

Va bene, la domanda non è così importante. Ma volevo solo chiarire che non era quello a cui ti riferivi nell'ultima riga di codice.
Andrew Mao,

13

Un punto non menzionato in altre risposte che è importante notare è che le funzioni di associazione con la freccia grassa quando non è necessario possono portare a risultati non intenzionali come in questo esempio con una classe che chiameremo semplicemente 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

In questo caso le funzioni fanno esattamente ciò che ci si potrebbe aspettare e sembra che non ci siano perdite nell'uso della freccia grassa, ma cosa succede quando modifichiamo il prototipo DummyClass dopo che è già stato definito (ad esempio cambiando un avviso o cambiando l'output di un registro) :

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

Come possiamo vedere, l'override della nostra funzione del prototipo precedentemente definita fa sì che some_function venga sovrascritto correttamente, ma other_function rimane lo stesso nelle istanze in quanto fat arrow ha fatto sì che other_function della classe sia vincolata a tutte le istanze in modo che le istanze non facciano riferimento alla loro classe per trovare una funzione

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

Anche la freccia grassa non funzionerà poiché la freccia grassa fa sì che la funzione sia legata a nuove istanze (che ottengono le nuove funzioni come ci si aspetterebbe).

Tuttavia, ciò comporta alcuni problemi, e se avessimo bisogno di una funzione (ad esempio nel caso di passare una funzione di registrazione in una casella di output o qualcosa del genere) che funzionerà su tutte le istanze esistenti (inclusi i gestori di eventi) [come tale non possiamo usare frecce grasse nella definizione originale] ma abbiamo ancora bisogno di accedere agli attributi interni in un gestore di eventi [il motivo esatto per cui abbiamo usato frecce grasse non frecce sottili].

Bene, il modo più semplice per raggiungere questo obiettivo è semplicemente includere due funzioni nella definizione della classe originale, una definita con una freccia sottile che esegue le operazioni che si desidera eseguire e un'altra definita con una freccia grassa che non fa altro che chiamare la prima funzione per esempio:

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

Quindi, quando usare le frecce sottili / grasse può essere riassunto abbastanza facilmente in quattro modi:

  1. Le funzioni della sola freccia sottile dovrebbero essere utilizzate quando entrambe le condizioni sono messe:

    • Il metodo non verrà mai passato per riferimento, inclusi event_handlers, ad es. Non hai mai un caso come: some_reference = some_instance.some_method; some_reference ()
    • E il metodo dovrebbe essere universale su tutte le istanze, quindi se la funzione prototipo cambia, cambia anche il metodo su tutte le istanze
  2. Le sole funzioni della freccia grassa dovrebbero essere utilizzate quando è soddisfatta la seguente condizione:

    • Il metodo deve essere associato precisamente all'istanza alla creazione dell'istanza e rimanere permanentemente associato anche se la definizione della funzione cambia per il prototipo, questo include tutti i casi in cui la funzione dovrebbe essere un gestore di eventi e il comportamento del gestore di eventi dovrebbe essere coerente
  3. La funzione della freccia grassa che chiama direttamente una funzione della freccia sottile dovrebbe essere utilizzata quando sono soddisfatte le seguenti condizioni:

    • Il metodo deve essere chiamato per riferimento come un gestore eventi
    • E la funzionalità potrebbe cambiare in futuro influenzando le istanze esistenti sostituendo la funzione freccia sottile
  4. La funzione di freccia sottile che chiama direttamente una funzione di freccia grassa (non dimostrata) deve essere utilizzata quando sono soddisfatte le seguenti condizioni:

    • La funzione della freccia grassa deve essere sempre collegata all'istanza
    • MA la funzione freccia sottile può cambiare (anche con una nuova funzione che non utilizza la funzione freccia grasso originale)
    • E la funzione freccia sottile non deve mai essere passata per riferimento

In tutti gli approcci deve essere considerato nel caso in cui le funzioni del prototipo possano essere modificate se il comportamento per istanze specifiche si comporterà o meno correttamente, ad esempio sebbene una funzione sia definita con una freccia spessa, il suo comportamento potrebbe non essere coerente all'interno di un'istanza se chiama un metodo che viene modificato all'interno del prototipo


9

Di solito ->va bene.

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

alert Foo.static() == Foo # true

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

Si noti come il metodo statico restituisce l'oggetto classe per thise l'istanza restituisce l'oggetto istanza per this.

Quello che sta succedendo è che la sintassi di invocazione sta fornendo il valore di this. In questo codice:

foo.bar()

foosarà il contesto della bar()funzione per impostazione predefinita. Quindi funziona come vuoi. È necessaria solo la freccia grassa quando si chiamano queste funzioni in qualche altro modo che non utilizza la sintassi del punto per l'invocazione.

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

In entrambi i casi, usare una freccia grossa per dichiarare che la funzione permetterebbe a quelli di funzionare. Ma a meno che tu non stia facendo qualcosa di strano, di solito non è necessario.

Quindi usa ->fino a quando non ne hai davvero bisogno =>e non usalo mai =>di default.


1
Questo fallirà se lo fai:x = obj.instance; alert x() == obj # false!
nicolaskruchten il

2
Certo che lo sarà, ma ciò rientrerebbe nel "fare qualcosa di sbagliato". Ho ora modificato la mia risposta e spiegato proprio quando =>sarebbe necessario sui metodi statici / di istanza di una classe.
Alex Wayne,

Nitpick: // is not a CoffeeScript commentconsiderando # is a CoffeeScript comment.
nicolaskruchten,

Come sta setTimeout foo.bar, 1000"sbagliando"? L'uso di una freccia grassa è molto più bello rispetto all'utilizzo di setTimeout (-> foo.bar()), 1000IMHO.
nicolaskruchten,

1
@nicolaskruchten Naturalmente c'è un caso per quella sintassi setTimeout. Ma il tuo primo commento è in qualche modo inventato e non rivela un caso d'uso legittimo, ma rivela semplicemente come potrebbe rompersi. Sto semplicemente dicendo che non dovresti usare un a =>meno che tu non ne abbia bisogno per una buona ragione, specialmente sui metodi di istanza di classe in cui ha un costo prestazionale per la creazione di una nuova funzione che deve essere vincolata all'istanza.
Alex Wayne,

5

solo un esempio per la freccia grassa indesiderata

non funziona: (@canvas undefined)

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

funziona: (definito @canvas)

class Test
  constructor: ->
    @canvas = document.createElement 'canvas'
    window.addEventListener 'resize', =>
      @canvas.width = window.innerWidth
      @canvas.height = window.innerHeight
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.