Quando si crea una classe in CoffeeScript, tutti i metodi di istanza dovrebbero essere definiti usando l' =>
operatore ("freccia grossa") e tutti i metodi statici definiti usando l' ->
operatore?
Quando si crea una classe in CoffeeScript, tutti i metodi di istanza dovrebbero essere definiti usando l' =>
operatore ("freccia grossa") e tutti i metodi statici definiti usando l' ->
operatore?
Risposte:
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 this
mentre la freccia sottile no, così i metodi a freccia sottile chiamati come callback come sopra non possono accedere ai campi dell'istanza come @msg
o chiamare altri metodi di istanza. L'ultima riga è una soluzione alternativa per i casi in cui è stata utilizzata la freccia sottile.
this
che verrebbe chiamato dalla freccia sottile, ma anche le variabili di istanza che otterrai con la freccia grassa?
this
impostato su una variabile che voglio usare. Tuttavia, voglio anche fare riferimento a un metodo di classe, quindi desidero this
fare 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?
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:
Le funzioni della sola freccia sottile dovrebbero essere utilizzate quando entrambe le condizioni sono messe:
Le sole funzioni della freccia grassa dovrebbero essere utilizzate quando è soddisfatta la seguente condizione:
La funzione della freccia grassa che chiama direttamente una funzione della freccia sottile dovrebbe essere utilizzata quando sono soddisfatte le seguenti condizioni:
La funzione di freccia sottile che chiama direttamente una funzione di freccia grassa (non dimostrata) deve essere utilizzata quando sono soddisfatte le seguenti condizioni:
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
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 this
e 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()
foo
sarà 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.
x = obj.instance; alert x() == obj # false!
=>
sarebbe necessario sui metodi statici / di istanza di una classe.
// is not a CoffeeScript comment
considerando # is a CoffeeScript comment
.
setTimeout foo.bar, 1000
"sbagliando"? L'uso di una freccia grassa è molto più bello rispetto all'utilizzo di setTimeout (-> foo.bar()), 1000
IMHO.
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.
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